@ 261106.14
current
/**
 * @license
 * Copyright 2017 The Emscripten Authors
 * SPDX-License-Identifier: MIT
 */

addToLibrary({
  ...defineInternalConsts({
    $PIPEFS_BUCKET_BUFFER_SIZE: 1024 * 8, // 8KiB Buffer
    $PIPEFS_CAPACITY: 1024 * 64, // as on Linux
    // Queued bytes above which a PIPE_BUF write no longer fits.
    $PIPEFS_THRESHOLD: 1024 * 64 - cDefs.PIPE_BUF,
  }),
  $PIPEFS__postset: () => addAtInit('PIPEFS.root = FS.mount(PIPEFS, {}, null);'),
  $PIPEFS: {
    mount(mount) {
      // Do not pollute the real root directory or its child nodes with pipes
      // Looks like it is OK to create another pseudo-root node not linked to the FS.root hierarchy this way
      return FS.createNode(null, '/', S_IFDIR | 0o777, 0);
    },
    createPipe() {
      var pipe = {
        buckets: [],
        // Open write ends. When it drops to 0 the reader sees EOF and poll must
        // report POLLHUP (Linux semantics). Buckets are freed once both counts
        // reach 0.
        writerCount: 1,
        writeClosed: false,
        // Open read ends. When it drops to 0 the writer sees POLLERR (a further
        // write would get EPIPE).
        readerCount: 1,
        readClosed: false,
        timestamp: new Date(),
      };

      pipe.buckets.push({
        buffer: new Uint8Array(PIPEFS_BUCKET_BUFFER_SIZE),
        offset: 0,
        roffset: 0
      });

      var rName = PIPEFS.nextname();
      var wName = PIPEFS.nextname();
      var rNode = FS.createNode(PIPEFS.root, rName, S_IFIFO, 0);
      var wNode = FS.createNode(PIPEFS.root, wName, S_IFIFO, 0);

      rNode.pipe = pipe;
      wNode.pipe = pipe;
      // The read end's node carries the reader poll wait-queue (writes wake it);
      // the write end's node carries the writer wait-queue (read-end close wakes it).
      pipe.readNode = rNode;
      pipe.writeNode = wNode;

      var readableStream = FS.createStream({
        path: rName,
        node: rNode,
        flags: O_RDONLY,
        seekable: false,
        stream_ops: PIPEFS.stream_ops
      });
      rNode.stream = readableStream;

      var writableStream = FS.createStream({
        path: wName,
        node: wNode,
        flags: O_WRONLY,
        seekable: false,
        stream_ops: PIPEFS.stream_ops
      });
      wNode.stream = writableStream;

      return {
        readable_fd: readableStream.fd,
        writable_fd: writableStream.fd
      };
    },
    queuedBytes(pipe) {
      var n = 0;
      for (var bucket of pipe.buckets) n += bucket.offset - bucket.roffset;
      return n;
    },
    stream_ops: {
      getattr(stream) {
        var node = stream.node;
        var timestamp = node.pipe.timestamp;
        return {
          dev: 14,
          ino: node.id,
          mode: 0o10600,
          nlink: 1,
          uid: 0,
          gid: 0,
          rdev: 0,
          size: 0,
          atime: timestamp,
          mtime: timestamp,
          ctime: timestamp,
          blksize: 4096,
          blocks: 0,
        };
      },
      poll(stream) {
        var pipe = stream.node.pipe;

        if ((stream.flags & O_ACCMODE) === O_WRONLY) {
          // Writable while a PIPE_BUF write fits. Linux keeps the write end
          // writable (the write itself fails with EPIPE) while also signalling
          // POLLERR once every read end is closed.
          var mask = 0;
          if (pipe.readClosed || PIPEFS.queuedBytes(pipe) <= PIPEFS_THRESHOLD) {
            mask = POLLWRNORM | POLLOUT;
          }
          if (pipe.readClosed) {
            mask |= POLLERR;
          }
          return mask;
        }
        var mask = 0;
        if (PIPEFS.queuedBytes(pipe)) {
          mask = POLLRDNORM | POLLIN;
        }
        // With every write end closed the read end is at EOF: readable (read
        // returns 0) and hung up.
        if (pipe.writeClosed) {
          mask |= POLLHUP | POLLIN;
        }
        return mask;
      },
      dup(stream) {
        var pipe = stream.node.pipe;
        if ((stream.flags & O_ACCMODE) === O_WRONLY) {
          pipe.writerCount++;
        } else {
          pipe.readerCount++;
        }
      },
      ioctl(stream, request, argp) {
        if (request == FIONREAD) {
          {{{ makeSetValue('argp', 0, 'PIPEFS.queuedBytes(stream.node.pipe)', 'i32') }}};
          return 0;
        }
        return EINVAL;
      },
      fsync(stream) {
        return EINVAL;
      },
      read(stream, buffer, offset, length, position /* ignored */) {
        var pipe = stream.node.pipe;
        var currentLength = PIPEFS.queuedBytes(pipe);

#if ASSERTIONS && !(MEMORY64 && MAXIMUM_MEMORY > FOUR_GB)
#if PTHREADS
        assert(buffer instanceof ArrayBuffer || buffer instanceof SharedArrayBuffer || ArrayBuffer.isView(buffer));
#else
        assert(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer));
#endif
#endif
        var data = buffer.subarray(offset, offset + length);

        if (length <= 0) {
          return 0;
        }
        if (currentLength == 0) {
          if (pipe.writeClosed) return 0;
          // Behave as if the read end is always non-blocking
          throw new FS.ErrnoError(EAGAIN);
        }
        var toRead = Math.min(currentLength, length);

        var totalRead = toRead;
        var toRemove = 0;

        for (var bucket of pipe.buckets) {
          var bucketSize = bucket.offset - bucket.roffset;

          if (toRead <= bucketSize) {
            var tmpSlice = bucket.buffer.subarray(bucket.roffset, bucket.offset);
            if (toRead < bucketSize) {
              tmpSlice = tmpSlice.subarray(0, toRead);
              bucket.roffset += toRead;
            } else {
              toRemove++;
            }
            data.set(tmpSlice);
            break;
          } else {
            var tmpSlice = bucket.buffer.subarray(bucket.roffset, bucket.offset);
            data.set(tmpSlice);
            data = data.subarray(tmpSlice.byteLength);
            toRead -= tmpSlice.byteLength;
            toRemove++;
          }
        }

        if (toRemove && toRemove == pipe.buckets.length) {
          // Do not generate excessive garbage in use cases such as
          // write several bytes, read everything, write several bytes, read everything...
          toRemove--;
          pipe.buckets[toRemove].offset = 0;
          pipe.buckets[toRemove].roffset = 0;
        }

        pipe.buckets.splice(0, toRemove);

        // Wake POLLOUT waiters on the transition to writable: listeners only
        // notify on readiness changes, and a waiter only exists while the
        // pipe is above the threshold.
        if (currentLength > PIPEFS_THRESHOLD && currentLength - totalRead <= PIPEFS_THRESHOLD) {
          pipe.writeNode.notifyListeners(POLLWRNORM | POLLOUT);
        }
        return totalRead;
      },
      write(stream, buffer, offset, length, position /* ignored */) {
        var pipe = stream.node.pipe;

#if ASSERTIONS && !(MEMORY64 && MAXIMUM_MEMORY > FOUR_GB)
#if PTHREADS
        assert(buffer instanceof ArrayBuffer || buffer instanceof SharedArrayBuffer || ArrayBuffer.isView(buffer));
#else
        assert(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer));
#endif
#endif
        var data = buffer.subarray(offset, offset + length);

        var dataLen = data.byteLength;
        if (dataLen <= 0) {
          return 0;
        }
        if (pipe.readClosed) {
          throw new FS.ErrnoError(EPIPE);
        }

        // PIPE_BUF rule: a small write is all-or-nothing, a larger one takes
        // what fits.
        if (stream.flags & O_NONBLOCK) {
          var room = PIPEFS_CAPACITY - PIPEFS.queuedBytes(pipe);
          if (room <= 0 || (dataLen <= PIPE_BUF && dataLen > room)) {
            throw new FS.ErrnoError(EAGAIN);
          }
          if (dataLen > room) {
            data = data.subarray(0, room);
            dataLen = room;
          }
        }

        var currBucket = null;

        if (pipe.buckets.length == 0) {
          currBucket = {
            buffer: new Uint8Array(PIPEFS_BUCKET_BUFFER_SIZE),
            offset: 0,
            roffset: 0
          };
          pipe.buckets.push(currBucket);
        } else {
          currBucket = pipe.buckets[pipe.buckets.length - 1];
        }

#if ASSERTIONS
        assert(currBucket.offset <= PIPEFS_BUCKET_BUFFER_SIZE);
#endif

        var freeBytesInCurrBuffer = PIPEFS_BUCKET_BUFFER_SIZE - currBucket.offset;
        if (freeBytesInCurrBuffer >= dataLen) {
          currBucket.buffer.set(data, currBucket.offset);
          currBucket.offset += dataLen;
          pipe.readNode.notifyListeners(POLLRDNORM | POLLIN);
          return dataLen;
        } else if (freeBytesInCurrBuffer > 0) {
          currBucket.buffer.set(data.subarray(0, freeBytesInCurrBuffer), currBucket.offset);
          currBucket.offset += freeBytesInCurrBuffer;
          data = data.subarray(freeBytesInCurrBuffer, data.byteLength);
        }

        var numBuckets = (data.byteLength / PIPEFS_BUCKET_BUFFER_SIZE) | 0;
        var remElements = data.byteLength % PIPEFS_BUCKET_BUFFER_SIZE;

        for (var i = 0; i < numBuckets; i++) {
          var newBucket = {
            buffer: new Uint8Array(PIPEFS_BUCKET_BUFFER_SIZE),
            offset: PIPEFS_BUCKET_BUFFER_SIZE,
            roffset: 0
          };
          pipe.buckets.push(newBucket);
          newBucket.buffer.set(data.subarray(0, PIPEFS_BUCKET_BUFFER_SIZE));
          data = data.subarray(PIPEFS_BUCKET_BUFFER_SIZE, data.byteLength);
        }

        if (remElements > 0) {
          var newBucket = {
            buffer: new Uint8Array(PIPEFS_BUCKET_BUFFER_SIZE),
            offset: data.byteLength,
            roffset: 0
          };
          pipe.buckets.push(newBucket);
          newBucket.buffer.set(data);
        }

        pipe.readNode.notifyListeners(POLLRDNORM | POLLIN);
        return dataLen;
      },
      close(stream) {
        var pipe = stream.node.pipe;
        // When the last write end closes, wake any poll/epoll waiter on the read
        // end with POLLHUP so a reader blocked on the writer dropping unblocks.
        if ((stream.flags & O_ACCMODE) === O_WRONLY) {
          if (!--pipe.writerCount) {
            pipe.writeClosed = true;
            pipe.readNode.notifyListeners(POLLHUP | POLLRDNORM | POLLIN);
          }
        } else if (!--pipe.readerCount) {
          // Mirror: when the last read end closes, wake any poll/epoll waiter on
          // the write end with POLLERR (a further write would get EPIPE).
          pipe.readClosed = true;
          pipe.writeNode.notifyListeners(POLLERR | POLLWRNORM | POLLOUT);
        }
        if (!pipe.readerCount && !pipe.writerCount) {
          pipe.buckets = null;
        }
      }
    },
    nextname() {
      if (!PIPEFS.nextname.current) {
        PIPEFS.nextname.current = 0;
      }
      return 'pipe[' + (PIPEFS.nextname.current++) + ']';
    },
  },
});