News for hackers. Stuff that matters.
Re:A Little Story About the `yes` Unix Command by crehn on Wednesday October 11, 2017 @09:01PM (#15454880)
Re:A Little Story About the `yes` Unix Command by glandium on Wednesday October 11, 2017 @09:52PM (#15455101)
FWIW, GNU true.c is that way because the GNU policy is for programs to all have --help and --version arguments.

Other fun fact, in some UNIXes, true looked(looks?) like:

  #!/bin/sh
  # Some multiple line copyright notice from AT&T
Yup, that's a shell script with only comments, all of which are a copyright notice for what is, essentially, an empty file. I saw that on Solaris something like 15 years ago. I'm sure it made its way to other flavors of UNIX.
Re:A Little Story About the `yes` Unix Command by ckuehl on Wednesday October 11, 2017 @10:53PM (#15455340)
There are some amusing notes about the /bin/true copyright claim here: http://trillian.mit.edu/~jc/humor/ATT_Copyright_true.html
Re:A Little Story About the `yes` Unix Command by kibwen on Wednesday October 11, 2017 @11:31PM (#15455465)
"Note also the claim that there is no warranty, which in this case presumably means that if the program actually does something, you can't sue them."

I love this.

Re:A Little Story About the `yes` Unix Command by kibwen on Wednesday October 11, 2017 @10:08PM (#15455161)
Now you've got me wondering what it would mean to try and enforce the copyright on an empty file. You'd think there would be some prior art, right? :)
Re:A Little Story About the `yes` Unix Command by robinhouston on Thursday October 12, 2017 @04:14AM (#15456432)
There’s a lovely story[1] from the early days of personal computing:

“GO.COM contained no program bytes at all – it was entirely empty. However, because GO.COM was empty, but still a valid program file as far as CP/M was concerned (it had a directory entry and file-name ending with .com), the CP/M loader, the part of the OS whose job it is to pull programs off disk and slap them into the TPA, would still load it!

So, how does this help? Well, using the scenario above:

• the user exited WordStar • the user ran DIR (or whatever else they needed) and at some future point would be ready to re-run Wordstar • the user now ‘loaded’ and ran GO.COM • the loader would load zero bytes of the GO.COM program off disk into the TPA – starting at address 0100h – and then jump to 0100h – to run the program it just loaded [GO.COM]! • result – it simply re-ran whatever was in the TPA when the user last exited to DOS – instantly [WordStar in this example]!

So, GO.COM, which consisted of zero bytes of code – and sold for £5 a copy is, I figure, the most profitable program ever written (as any other program will return mathematically fewer £s per byte than GO.COM did)!”

1. http://web.archive.org/web/20160304014157/http://peetm.com/b...

Re:A Little Story About the `yes` Unix Command by exikyut on Saturday October 14, 2017 @12:37AM (#15470793)
Thanks! I remember reading about this a while back, but lost the link.
Re:A Little Story About the `yes` Unix Command by perl4ever on Wednesday October 11, 2017 @10:33PM (#15455261)
Empty files remind me of an episode where I worked. We were supposed to provide a set of files for a court case. But there was a missing zero byte file due to a glitch in processing. So just create one, right? NOPE. The client insisted we find and copy the original empty file from the original media.
Re:A Little Story About the `yes` Unix Command by taneq on Wednesday October 11, 2017 @10:37PM (#15455275)
You just know it's because, if you just made a new zero-byte file, the opposition in the case would claim that you forged the file and that the evidence was then tainted. Stupid, but your goal here isn't to reproduce the file, it's to stop a stupid argument from convincing stupid people to make a stupid decision.
Re:A Little Story About the `yes` Unix Command by fludlight on Thursday October 12, 2017 @02:06AM (#15455986)
> your goal...[is] to stop a stupid argument from convincing stupid people to make a stupid decision.

This is the best explanation of legal practice I have ever seen.

Re:A Little Story About the `yes` Unix Command by mort96 on Thursday October 12, 2017 @05:03AM (#15456627)
But... to be sent, the file is recreated a bunch of times right? Because a computer only ever copies, and never “moves”, right? How can it then possibly matter whether the 0b file was recreated by a computer from the instruction of copying the file (which then just turns into an instruction to create the file), or if it was created directly through instructing the computer to create the file?

Gah, something something colored bits. http://ansuz.sooke.bc.ca/entry/23

Re:A Little Story About the `yes` Unix Command by randallsquared on Thursday October 12, 2017 @10:27AM (#15458159)
Computers do "move" files, in some cases, by updating the entry in the filesystem registry. In such a case, it might be that no change was made to the file on disk, so no copy was made at all.
Re:A Little Story About the `yes` Unix Command by mort96 on Thursday October 12, 2017 @01:42PM (#15459728)
I interpreted "provide a set of files for a court case" as some form of sending it either through the Internet or to some portable storage media. He also said "The client insisted we find and copy the original empty file from the original media", which also indicates that. I therefore intended "move" to mean "move from one storage media/computer to another". You're of course correct that if you "move" a file on one file system, the bits won't actually be copied (or moved) anywhere.
Re:A Little Story About the `yes` Unix Command by randallsquared on Thursday October 12, 2017 @04:52PM (#15461224)
Sure, no argument. I was responding only to the "computer only ever copies" statement. Sometimes it's hard to know how literal someone is being. :)
Re:A Little Story About the `yes` Unix Command by astrodust on Wednesday October 11, 2017 @11:40PM (#15455489)
It's like homeopathic copying: A zero byte string can contain trace amounts of the original.
Re:A Little Story About the `yes` Unix Command by TeMPOraL on Thursday October 12, 2017 @04:51AM (#15456582)
You wanted to provide a zero-byte file of the wrong colour. Unlike in physical reality, in legal world it strongly matters what colors are your bits (or in this case, what color is the lack of them).

http://ansuz.sooke.bc.ca/entry/23

Re:A Little Story About the `yes` Unix Command by jl6 on Thursday October 12, 2017 @01:14AM (#15455819)
Not a wholly insane request if they were interested in the file metadata as much as the file data.
Re:A Little Story About the `yes` Unix Command by perl4ever on Thursday October 12, 2017 @06:47AM (#15457014)
You might think, but metadata travels separately from the actual file, so that wasn't a factor.
Re:A Little Story About the `yes` Unix Command by coldtea on Thursday October 12, 2017 @07:32AM (#15457206)
Depending on how it's copied, they can be preserved.
Re:A Little Story About the `yes` Unix Command by xelxebar on Thursday October 12, 2017 @07:45AM (#15457264)
Well, I can see the point of leaving a trail that shows you aren't fraudulently claiming the file is zero bytes. Otherwise, it's just your word.
Re:A Little Story About the `yes` Unix Command by nucleardog on Thursday October 12, 2017 @04:42PM (#15461156)
Yeah, if they didn't find a copy on the backup/original media to confirm it's zero bytes, then really all they've provided is their assumption that it's zero bytes.

I'm no lawyer, but the testimony of the IT guy (and associated reasoning/etc) seems like a totally different kind of evidence than a true and accurate copy of a file from an original medium. And having the IT guy testifying seems like way more of a pain than just making him find the file.

Re:A Little Story About the `yes` Unix Command by matt4077 on Thursday October 12, 2017 @09:07AM (#15457649)
"prior art" is a term concerning patents, whereas this is copyright.

There needs to be a certain level of creative work to qualify for copyright, and an empty program that does nothing is rather unlikely to qualify.

The definite case on that subject must be John Cage's "4:33", which is 4 minutes and 33 seconds of silence. Its copyright has been upheld in court. Yes, the estate of John Cage sued somebody who "quoted" to much of his work of silence.

That sounds somewhat silly, yet it's well-reasoned: the infringing artist actually acknowledged Cage in the CD inset. And 4'33 isn't really only silence. Audio recordings feature the pianist sitting down, open and closing the piano cover, and the audience making various noises. It's somewhat "out there" obviously, but just the fact that it's rather well-known kind of shows that it did what it was supposed to do.

Re:A Little Story About the `yes` Unix Command by wahern on Thursday October 12, 2017 @01:39AM (#15455897)
I'm surprised that neither GNU yes nor GNU cat uses splice(2). Here's what I get. Note that ./rust is the final Rust program from the article modified to print stats and exit after a set byte count[1], ./splice is a simple C program that uses splice to copy the input string from a generated anonymous temporary file to stdout, and ./consume uses splice to move data from stdin to /dev/null. In all tests the default string of "y\n" is used.

  $ ./rust | cat >/dev/null
  5.220 GB/s (8589934592 bytes in 1.645 seconds)

  $ ./splice | cat >/dev/null
  14.514 GB/s (8589934592 bytes in 0.551 seconds)

  $ ./rust | ./consume 
  9.312 GB/s (8589934592 bytes in 0.922 seconds)

  $ ./splice | ./consume 
  25.118 GB/s (8589934592 bytes in 0.318 seconds)
As you can see, cat(1) is the real bottleneck.

splice is Linux-only. Most systems have sendfile(2), including Linux, but I didn't test it. The implementation and semantics of sendfile vary across platforms.

[1] I did this before I wrote ./consume (which takes an optional byte count limit), having assumed that GNU cat was using splice. As cat doesn't have a way to limit the number of bytes read/written, and other tools like head or tail definitely don't use splice, the simplest way to limit the benchmark without introducing a bottleneck was to have the producers themselves print stats and exit. The relevant diff is

    + let mut count = 0u64 as usize;
    + let bytes = (1u64 << 33) as usize;
    ...
    -while locked.write_all(filled).is_ok() {}
    +while locked.write_all(filled).is_ok() {
    +  count += filled.len();
    +  if count >= bytes {
    +      break;
    +  }
    +}
Re:A Little Story About the `yes` Unix Command by pwg on Thursday October 12, 2017 @06:37AM (#15456971)
> I'm surprised that neither GNU yes nor GNU cat uses splice(2). ... splice is Linux-only.

The fact that splice is Linux only is most likely why. GNU programs are portable to many different OSes, many of which no longer exist in any real form. They try to not use any 'single OS' specific feature if at all possible.

Re:A Little Story About the `yes` Unix Command by wahern on Thursday October 12, 2017 @01:17PM (#15459541)
splice(2) and tee(2) were basically tailor made for cat(1) and tee(1), and for some reason I was under the impression that GNU tee was using splice(2) and/or tee(2). Using these could be trivial--just a few extra lines of code that could fall-through to the existing methods, for a huge speed-up in performance. (Performance matters because the consumers could be CPU bound, and an inefficient cat or tee might be taking away resources that could be used by the consumer.)

Regarding portability, GNU tail uses the Linux-specific inotify(2) to respond faster to writes. Like alot of OSS, coreutils uses the BSD .d_type member extension[1] of struct dirent to avoid unnecessary stat() calls. There are many other more intrusive OS-specific details baked into coreutils, but often it's the nature of the problem--in many situations you're dependent on platform-specific details or features. For the most part, these nitty-gritty platform-specific details are far more intrusive in terms of code complexity than the performance optimizations.

[1] Missing from Solaris, and probably most other SysV derivatives.

Re:A Little Story About the `yes` Unix Command by glandium on Thursday October 12, 2017 @08:26AM (#15457445)
Most importantly, GNU programs ought to work on Hurd.
Re:A Little Story About the `yes` Unix Command by Y_Y on Thursday October 12, 2017 @04:54AM (#15456593)
Forgive me if I'm missing something subtle, but aren't the first two examples Useless Use Of Cat?
Re:A Little Story About the `yes` Unix Command by jwilk on Thursday October 12, 2017 @05:10AM (#15456661)
No, the use of cat is intentional.

The output of yes(1) is normally consumed by another program, not redirected to a file, so this is what the examples emulate.

Re:A Little Story About the `yes` Unix Command by isatty on Thursday October 12, 2017 @04:58AM (#15456609)
Yes they are. Why not just send it straight to /dev/null?
Re:A Little Story About the `yes` Unix Command by wahern on Thursday October 12, 2017 @12:59PM (#15459405)
1) Because splice(2) requires either the source or sink to be a pipe. The source is a regular file so the sink has to be a pipe.

2) Because the example Rust program(s), emulating yes(1), had no [simple] way to measure throughput except by piping to another program. We can't fairly compare program A that writes directly to /dev/null with program B that writes to a pipe even if program A can measure its throughput.

2a) What jwilk said.

3) For some reason I thought that glibc had optimizations to elide fwrites to /dev/null, and some of my code was using stdio (e.g. for the final trailing bytes less than the pagesize). I could've sworn either glibc or bash did this, but I can't find any mention of it, now. I realize it would be crazy difficult and ugly for glibc to do this (because of dup2, etc), but glibc does alot of crazy things, and in any event I didn't bother checking beforehand.

Mostly it comes down to fairly comparing benchmarks and kernel facilities. Otherwise, yes, those would be classic examples of Useless Use of Cat.

Re:A Little Story About the `yes` Unix Command by jwilk on Thursday October 12, 2017 @05:08AM (#15456650)
Show us the source of your programs. :)
Re:A Little Story About the `yes` Unix Command by wahern on Thursday October 12, 2017 @04:06PM (#15460895)
Here's a cleanup version:

  #ifndef _GNU_SOURCE
  #define _GNU_SOURCE 1
  #endif
  
  #include <errno.h>    /* ERANGE errno */
  #include <inttypes.h> /* strtoumax(3) */
  #include <limits.h>   /* LONG_MAX */
  #include <stdint.h>   /* uintmax_t */
  #include <stdio.h>    /* fflush(3) fprintf(3) fread(3) fwrite(3) tmpfile(3) */
  #include <stdlib.h>   /* EXIT_FAILURE */
  #include <string.h>   /* strlen(3) */
  
  #include <err.h>      /* err(3) errx(3) */
  #include <fcntl.h>    /* loff_t splice(2) */
  #include <sys/time.h> /* struct timeval gettimeofday(2) */
  #include <unistd.h>   /* _SC_PAGESIZE pread(2) sysconf(3) write(2) */
  
  #ifndef HAVE_SPLICE
  #define HAVE_SPLICE __linux__
  #endif
  
  #define MIN(a, b) (((a) < (b))? (a) : (b))
  #define MAX(a, b) (((a) > (b))? (a) : (b))
  
  #ifndef howmany
  #define howmany(x, y)  (((x) + ((y) - 1)) / (y))
  #endif
  
  #define UMAX_PREC (sizeof (uintmax_t) * CHAR_BIT) /* NB: assumes no padding */
  #define UMAX_HALF ((UMAX_PREC + 1) / 2)
  #define UMAX_LO(n) ((n) & ((UINTMAX_C(1) << UMAX_HALF) - 1))
  #define UMAX_HI(n) ((n) >> UMAX_HALF)
  
  static inline _Bool
  add_overflow(uintmax_t *r, const uintmax_t a, const uintmax_t b)
  {
          if (~a < b)
                  return 1;
          *r = a + b;
          return 0;
  }
  
  /*
   * Implement multiplication using a polynomial with four multiplications and
   * three additions, except we can optimize out some operations.
   */
  static inline _Bool
  mul_overflow(uintmax_t *_r, const uintmax_t _a, const uintmax_t _b)
  {
    uintmax_t a[2] = { UMAX_LO(_a), UMAX_HI(_a) };
    uintmax_t b[2] = { UMAX_LO(_b), UMAX_HI(_b) };
    uintmax_t r[2];
  
    /* if both are non-0, we'd always overflow */
    if (a[1] && b[1])
      return 1;
  
    /* either a[1] or b[1] must be 0 here, so no intermediate overflow */
    r[1] = (a[1] * b[0]) + (a[0] * b[1]);
  
    /* if the result has MSW bits set, we'd overflow */
    if (UMAX_HI(r[1]))
      return 1;
  
    r[0] = a[0] * b[0];
  
    return add_overflow(_r, r[0], r[1] << UMAX_HALF);
  }
  
  #if 0
  static uintmax_t
  add(uintmax_t a, uintmax_t b)
  {
    uintmax_t r;
    if (add_overflow(&r, a, b))
      errx(1, "arithmetic overflow (%ju * %ju)", a, b);
    return r;
  }
  #endif
  
  static uintmax_t
  mul(uintmax_t a, uintmax_t b)
  {
    uintmax_t r;
    if (mul_overflow(&r, a, b))
      errx(1, "arithmetic overflow (%ju * %ju)", a, b);
    return r;
  }
  
  static uintmax_t
  toumax(const char *p)
  {
    char *pe;
    uintmax_t n;
  
    errno = 0;
    if (UINTMAX_MAX == (n = strtoumax(p, &pe, 10)) && errno != 0)
      err(1, "%s", p);
    if (*pe != '\0' || p == pe)
      errx(1, "%s: invalid number", p);
  
    return n;
  }
  
  static uintmax_t
  gcd(uintmax_t a, uintmax_t b)
  {
    uintmax_t c;
  
    while (a != 0) {
      c = a;
      a = b % a;
      b = c;
    }
  
    return b;
  }
  
  static uintmax_t
  lcm(uintmax_t a, uintmax_t b)
  {
    return mul(a, b) / gcd(a, b);
  }
  
  static size_t
  xgetpagesize(void)
  {
    long n = sysconf(_SC_PAGESIZE);
    if (n <= 0)
      err(1, "sysconf");
    return n;
  }
  
  static double
  gtod(void)
  {
    struct timeval now;
    gettimeofday(&now, NULL);
    return now.tv_sec + (now.tv_usec / 1000000.0);
  }
  
  int
  main(int argc, char **argv)
  {
    _Bool cflag = 0, nflag = 0;
    uintmax_t count = UINTMAX_MAX, lines = 0, total = 0;
    int optc;
  
    while (-1 != (optc = getopt(argc, argv, "c:n:"))) {
      switch (optc) {
      case 'c':
        if (nflag)
          errx(1, "-c and -n mutually exclusive");
        count = toumax(optarg);
        cflag = 1;
        break;
                  case 'n':
                    if (cflag)
          errx(1, "-c and -n mutually exclusive");
        lines = toumax(optarg);
        nflag = 1;
        break;
      default:
        return EXIT_FAILURE;
      }
    }
    argc -= optind;
    argv += optind;
  
    const char *const line = (argc > 0)? argv[0] : "y";
    const size_t linesize = strlen(line) + 1;
    const size_t pagesize = xgetpagesize();
    const size_t blocksize = MAX(mul(pagesize, 16), lcm(linesize, pagesize));
    const uintmax_t filesize = mul(blocksize, howmany((1UL << 20), blocksize));
  
    if (nflag)
      count = mul(linesize, lines);
  
  //  fprintf(stderr, "linesize:  %zu\n", linesize);
  //  fprintf(stderr, "pagesize:  %zu\n", xgetpagesize());
  //  fprintf(stderr, "blocksize: %ju\n", blocksize);
  //  fprintf(stderr, "filesize:  %ju\n", filesize);
  
    FILE *fh;
    if (!(fh = tmpfile()))
      err(1, "tmpfile");
    for (size_t i = 0; i < filesize; i += linesize) {
      int n = fprintf(fh, "%s\n", line);
      if (n == -1)
        err(1, "fprintf");
      if ((size_t)n != linesize)
        errx(1, "wrote %d bytes, expected %zu", n, linesize);
    }
    if (0 != fflush(fh))
      err(1, "fflush");
  
    double begin = gtod();
  #if HAVE_SPLICE
    int fd = fileno(fh);
    _Bool eof = 0;
    do {
      loff_t p = 0;
      size_t r;
      ssize_t n;
  
      /* NB: no LOFF_MAX available */
      _Static_assert(sizeof p <= sizeof (long), "unexpected type for loff_t");
      if (filesize > LONG_MAX)
        errx(1, "filesize too large (%ju > %jd)", filesize, (intmax_t)LONG_MAX);
  
      while ((size_t)p < filesize && count >= blocksize && !eof) {
        r = MIN(blocksize, count);
        r -= r % blocksize;
        n = splice(fd, &p, STDOUT_FILENO, NULL, r, SPLICE_F_MOVE);
        if (n == -1)
          err(1, "splice");
        p += n;
        count -= n;
        total += n;
        eof = n == 0;
      }
    } while (!eof && (count || (!cflag && !nflag)));
  #endif
    {
      char buf[BUFSIZ], *p, *pe;
      size_t n;
  
      while (count) {
        rewind(fh);
        if (!(n = fread(buf, 1, MIN(sizeof buf, count), fh))) {
          if (ferror(fh))
            err(1, "fread");
          break;
        }
  
        p = buf;
        pe = &buf[n];
        while (p < pe) {
          if (!(n = fwrite(p, 1, (size_t)(pe - p), stdout)))
            err(1, "fwrite");
          p += n;
          count -= n;
          total += n;
        }
      }
    }
    if (0 != fflush(stdout))
      err(1, "fflush");
  
    double elapsed = gtod() - begin;
    fprintf(stderr, "%.3f GB/s (%zu bytes in %.3f seconds)\n", ((double)total / elapsed) / (1UL<<30), total, elapsed);
  
    return 0;
  }
Re:A Little Story About the `yes` Unix Command by aleden on Thursday October 12, 2017 @10:39AM (#15458270)
hmm going in the direction of [binary] instead of [shell script], why not just go further and ditch the whole C stdlib by passing -ffreestanding and then compiling

  void _start(void) {
    exit(0);
  }
Re:A Little Story About the `yes` Unix Command by halfelf on Thursday October 12, 2017 @02:02AM (#15455965)
Once read similar things before, on reddit: https://www.reddit.com/r/unix/comments/6gxduc/how_is_gnu_yes...
Re:A Little Story About the `yes` Unix Command by noobermin on Thursday October 12, 2017 @01:06AM (#15455796)
That comment on the bsd one from theo is rather funny.
Re:A Little Story About the `yes` Unix Command by sgt on Thursday October 12, 2017 @03:41AM (#15456317)
On my Mac I'm getting

$ yes | pv -r > /dev/null

[34.9MiB/s]