Commit 81cc0be for zlib

commit 81cc0bebedd935daeb81b0b6e475d8786b51af3d
Author: Mark Adler <git@madler.net>
Date:   Sun May 25 19:01:36 2025 -0700

    Support non-blocking devices in the gz* routines.

diff --git a/gzguts.h b/gzguts.h
index b460912..c3d473a 100644
--- a/gzguts.h
+++ b/gzguts.h
@@ -185,6 +185,7 @@ typedef struct {
         /* just for reading */
     int junk;               /* -1 = start, 1 = junk candidate, 0 = in gzip */
     int how;                /* 0: get header, 1: copy, 2: decompress */
+    int again;              /* true if EAGAIN or EWOULDBLOCK on last i/o */
     z_off64_t start;        /* where the gzip data started, for rewinding */
     int eof;                /* true if end of input file reached */
     int past;               /* true if read requested past end */
diff --git a/gzlib.c b/gzlib.c
index 3c50aff..65a0b49 100644
--- a/gzlib.c
+++ b/gzlib.c
@@ -52,7 +52,7 @@ char ZLIB_INTERNAL *gz_strwinerror(DWORD error) {
             msgbuf[chars] = 0;
         }

-        wcstombs(buf, msgbuf, chars + 1);       // assumes buf is big enough
+        wcstombs(buf, msgbuf, chars + 1);       /* assumes buf is big enough */
         LocalFree(msgbuf);
     }
     else {
@@ -76,6 +76,7 @@ local void gz_reset(gz_statep state) {
     }
     else                            /* for writing ... */
         state->reset = 0;           /* no deflateReset pending */
+    state->again = 0;               /* no stalled i/o yet */
     state->skip = 0;                /* no seek request pending */
     gz_error(state, Z_OK, NULL);    /* clear error */
     state->x.pos = 0;               /* no uncompressed data yet */
@@ -86,10 +87,7 @@ local void gz_reset(gz_statep state) {
 local gzFile gz_open(const void *path, int fd, const char *mode) {
     gz_statep state;
     z_size_t len;
-    int oflag;
-#ifdef O_CLOEXEC
-    int cloexec = 0;
-#endif
+    int oflag = 0;
 #ifdef O_EXCL
     int exclusive = 0;
 #endif
@@ -135,7 +133,7 @@ local gzFile gz_open(const void *path, int fd, const char *mode) {
                 break;
 #ifdef O_CLOEXEC
             case 'e':
-                cloexec = 1;
+                oflag |= O_CLOEXEC;
                 break;
 #endif
 #ifdef O_EXCL
@@ -158,6 +156,11 @@ local gzFile gz_open(const void *path, int fd, const char *mode) {
             case 'G':
                 state->direct = -1;
                 break;
+#ifdef O_NONBLOCK
+            case 'N':
+                oflag |= O_NONBLOCK;
+                break;
+#endif
             case 'T':
                 state->direct = 1;
                 break;
@@ -223,15 +226,12 @@ local gzFile gz_open(const void *path, int fd, const char *mode) {
     }

     /* compute the flags for open() */
-    oflag =
+    oflag |=
 #ifdef O_LARGEFILE
         O_LARGEFILE |
 #endif
 #ifdef O_BINARY
         O_BINARY |
-#endif
-#ifdef O_CLOEXEC
-        (cloexec ? O_CLOEXEC : 0) |
 #endif
         (state->mode == GZ_READ ?
          O_RDONLY :
@@ -250,8 +250,17 @@ local gzFile gz_open(const void *path, int fd, const char *mode) {
     else if (fd == -2)
         state->fd = _wopen(path, oflag, _S_IREAD | _S_IWRITE);
 #endif
-    else
+    else {
+#ifdef O_NONBLOCK
+        if (oflag & O_NONBLOCK)
+            fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+#endif
+#ifdef O_CLOEXEC
+        if (oflag & O_CLOEXEC)
+            fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | O_CLOEXEC);
+#endif
         state->fd = fd;
+    }
     if (state->fd == -1) {
         free(state->path);
         free(state);
@@ -552,7 +561,7 @@ void ZLIB_INTERNAL gz_error(gz_statep state, int err, const char *msg) {
     }

     /* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */
-    if (err != Z_OK && err != Z_BUF_ERROR)
+    if (err != Z_OK && err != Z_BUF_ERROR && !state->again)
         state->x.have = 0;

     /* set error code, and if no message, then done */
@@ -589,6 +598,7 @@ unsigned ZLIB_INTERNAL gz_intmax(void) {
     return INT_MAX;
 #else
     unsigned p = 1, q;
+
     do {
         q = p;
         p <<= 1;
diff --git a/gzread.c b/gzread.c
index aca4186..025657a 100644
--- a/gzread.c
+++ b/gzread.c
@@ -8,12 +8,20 @@
 /* Use read() to load a buffer -- return -1 on error, otherwise 0.  Read from
    state->fd, and update state->eof, state->err, and state->msg as appropriate.
    This function needs to loop on read(), since read() is not guaranteed to
-   read the number of bytes requested, depending on the type of descriptor. */
+   read the number of bytes requested, depending on the type of descriptor. It
+   also needs to loop to manage the fact that read() returns an int. If the
+   descriptor is non-blocking and read() returns with no data in order to avoid
+   blocking, then gz_load() will return 0 if some data has been read, or -1 if
+   no data has been read. Either way, state->again is set true to indicate a
+   non-blocking event. If errno is non-zero on return, then there was an error
+   signaled from read().  *have is set to the number of bytes read. */
 local int gz_load(gz_statep state, unsigned char *buf, unsigned len,
                   unsigned *have) {
     int ret;
     unsigned get, max = ((unsigned)-1 >> 2) + 1;

+    state->again = 0;
+    errno = 0;
     *have = 0;
     do {
         get = len - *have;
@@ -25,6 +33,11 @@ local int gz_load(gz_statep state, unsigned char *buf, unsigned len,
         *have += (unsigned)ret;
     } while (*have < len);
     if (ret < 0) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            state->again = 1;
+            if (*have != 0)
+                return 0;
+        }
         gz_error(state, Z_ERRNO, zstrerror());
         return -1;
     }
@@ -50,8 +63,10 @@ local int gz_avail(gz_statep state) {
         if (strm->avail_in) {       /* copy what's there to the start */
             unsigned char *p = state->in;
             unsigned const char *q = strm->next_in;
+
             if (q != p) {
                 unsigned n = strm->avail_in;
+
                 do {
                     *p++ = *q++;
                 } while (--n);
@@ -125,7 +140,9 @@ local int gz_look(gz_statep state) {
        then it's not an error as this is a transparent read of zero bytes */
     if (gz_avail(state) == -1)
         return -1;
-    if (strm->avail_in == 0)
+    if (strm->avail_in == 0 || (state->again && strm->avail_in < 4))
+        /* if non-blocking input stalled before getting four bytes, then
+           return and wait until a later call has accumulated enough */
         return 0;

     /* see if this is (likely) gzip input -- if the first four bytes are
@@ -154,9 +171,12 @@ local int gz_look(gz_statep state) {

 /* Decompress from input to the provided next_out and avail_out in the state.
    On return, state->x.have and state->x.next point to the just decompressed
-   data.  If the gzip stream completes, state->how is reset to LOOK to look for
-   the next gzip stream or raw data, once state->x.have is depleted.  Returns 0
-   on success, -1 on failure. */
+   data. If the gzip stream completes, state->how is reset to LOOK to look for
+   the next gzip stream or raw data, once state->x.have is depleted. Returns 0
+   on success, -1 on failure. If EOF is reached when looking for more input to
+   complete the gzip member, then an unexpected end of file error is raised.
+   If there is no more input, but state->again is true, then EOF has not been
+   reached, and no error is raised. */
 local int gz_decomp(gz_statep state) {
     int ret = Z_OK;
     unsigned had;
@@ -171,7 +191,8 @@ local int gz_decomp(gz_statep state) {
             break;
         }
         if (strm->avail_in == 0) {
-            gz_error(state, Z_BUF_ERROR, "unexpected end of file");
+            if (!state->again)
+                gz_error(state, Z_BUF_ERROR, "unexpected end of file");
             break;
         }

@@ -371,15 +392,17 @@ local z_size_t gz_read(gz_statep state, voidp buf, z_size_t len) {
 int ZEXPORT gzread(gzFile file, voidp buf, unsigned len) {
     gz_statep state;

-    /* get internal structure */
+    /* get internal structure and check that it's for reading */
     if (file == NULL)
         return -1;
     state = (gz_statep)file;
+    if (state->mode != GZ_READ)
+        return -1;

-    /* check that we're reading and that there's no (serious) error */
-    if (state->mode != GZ_READ ||
-            (state->err != Z_OK && state->err != Z_BUF_ERROR))
+    /* check that there was no (serious) error */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
         return -1;
+    gz_error(state, Z_OK, NULL);

     /* since an int is returned, make sure len fits in one, otherwise return
        with an error (this avoids a flaw in the interface) */
@@ -389,13 +412,22 @@ int ZEXPORT gzread(gzFile file, voidp buf, unsigned len) {
     }

     /* read len or fewer bytes to buf */
-    len = (unsigned)gz_read(state, buf, len);
+    len = gz_read(state, buf, len);

     /* check for an error */
-    if (len == 0 && state->err != Z_OK && state->err != Z_BUF_ERROR)
-        return -1;
+    if (len == 0) {
+        if (state->err != Z_OK && state->err != Z_BUF_ERROR)
+            return -1;
+        if (state->again) {
+            /* non-blocking input stalled after some input was read, but no
+               uncompressed bytes were produced -- let the application know
+               this isn't EOF */
+            gz_error(state, Z_ERRNO, zstrerror());
+            return -1;
+        }
+    }

-    /* return the number of bytes read (this is assured to fit in an int) */
+    /* return the number of bytes read */
     return (int)len;
 }

@@ -405,15 +437,17 @@ z_size_t ZEXPORT gzfread(voidp buf, z_size_t size, z_size_t nitems,
     z_size_t len;
     gz_statep state;

-    /* get internal structure */
+    /* get internal structure and check that it's for reading */
     if (file == NULL)
         return 0;
     state = (gz_statep)file;
+    if (state->mode != GZ_READ)
+        return 0;

-    /* check that we're reading and that there's no (serious) error */
-    if (state->mode != GZ_READ ||
-            (state->err != Z_OK && state->err != Z_BUF_ERROR))
+    /* check that there was no (serious) error */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
         return 0;
+    gz_error(state, Z_OK, NULL);

     /* compute bytes to read -- error on overflow */
     len = nitems * size;
@@ -436,15 +470,17 @@ int ZEXPORT gzgetc(gzFile file) {
     unsigned char buf[1];
     gz_statep state;

-    /* get internal structure */
+    /* get internal structure and check that it's for reading */
     if (file == NULL)
         return -1;
     state = (gz_statep)file;
+    if (state->mode != GZ_READ)
+        return -1;

-    /* check that we're reading and that there's no (serious) error */
-    if (state->mode != GZ_READ ||
-        (state->err != Z_OK && state->err != Z_BUF_ERROR))
+    /* check that there was no (serious) error */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
         return -1;
+    gz_error(state, Z_OK, NULL);

     /* try output buffer (no need to check for skip request) */
     if (state->x.have) {
@@ -465,19 +501,21 @@ int ZEXPORT gzgetc_(gzFile file) {
 int ZEXPORT gzungetc(int c, gzFile file) {
     gz_statep state;

-    /* get internal structure */
+    /* get internal structure and check that it's for reading */
     if (file == NULL)
         return -1;
     state = (gz_statep)file;
+    if (state->mode != GZ_READ)
+        return -1;

     /* in case this was just opened, set up the input buffer */
-    if (state->mode == GZ_READ && state->how == LOOK && state->x.have == 0)
+    if (state->how == LOOK && state->x.have == 0)
         (void)gz_look(state);

-    /* check that we're reading and that there's no (serious) error */
-    if (state->mode != GZ_READ ||
-        (state->err != Z_OK && state->err != Z_BUF_ERROR))
+    /* check that there was no (serious) error */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
         return -1;
+    gz_error(state, Z_OK, NULL);

     /* process a skip request */
     if (state->skip && gz_skip(state) == -1)
@@ -507,6 +545,7 @@ int ZEXPORT gzungetc(int c, gzFile file) {
     if (state->x.next == state->out) {
         unsigned char *src = state->out + state->x.have;
         unsigned char *dest = state->out + (state->size << 1);
+
         while (src > state->out)
             *--dest = *--src;
         state->x.next = dest;
@@ -526,23 +565,25 @@ char * ZEXPORT gzgets(gzFile file, char *buf, int len) {
     unsigned char *eol;
     gz_statep state;

-    /* check parameters and get internal structure */
+    /* check parameters, get internal structure, and check that it's for
+       reading */
     if (file == NULL || buf == NULL || len < 1)
         return NULL;
     state = (gz_statep)file;
+    if (state->mode != GZ_READ)
+        return NULL;

-    /* check that we're reading and that there's no (serious) error */
-    if (state->mode != GZ_READ ||
-        (state->err != Z_OK && state->err != Z_BUF_ERROR))
+    /* check that there was no (serious) error */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR && !state->again)
         return NULL;
+    gz_error(state, Z_OK, NULL);

     /* process a skip request */
     if (state->skip && gz_skip(state) == -1)
         return NULL;

-    /* copy output bytes up to new line or len - 1, whichever comes first --
-       append a terminating zero to the string (we don't check for a zero in
-       the contents, let the user worry about that) */
+    /* copy output up to a new line, len-1 bytes, or there is no more output,
+       whichever comes first */
     str = buf;
     left = (unsigned)len - 1;
     if (left) do {
@@ -569,7 +610,9 @@ char * ZEXPORT gzgets(gzFile file, char *buf, int len) {
         buf += n;
     } while (left && eol == NULL);

-    /* return terminated string, or if nothing, end of file */
+    /* append a terminating zero to the string (we don't check for a zero in
+       the contents, let the user worry about that) -- return the terminated
+       string, or if nothing was read, NULL */
     if (buf == str)
         return NULL;
     buf[0] = 0;
@@ -599,12 +642,10 @@ int ZEXPORT gzclose_r(gzFile file) {
     int ret, err;
     gz_statep state;

-    /* get internal structure */
+    /* get internal structure and check that it's for reading */
     if (file == NULL)
         return Z_STREAM_ERROR;
     state = (gz_statep)file;
-
-    /* check that we're reading */
     if (state->mode != GZ_READ)
         return Z_STREAM_ERROR;

diff --git a/gzwrite.c b/gzwrite.c
index ab29bf4..e7864cb 100644
--- a/gzwrite.c
+++ b/gzwrite.c
@@ -74,9 +74,13 @@ local int gz_comp(gz_statep state, int flush) {
     /* write directly if requested */
     if (state->direct) {
         while (strm->avail_in) {
+            errno = 0;
+            state->again = 0;
             put = strm->avail_in > max ? max : strm->avail_in;
             writ = write(state->fd, strm->next_in, put);
             if (writ < 0) {
+                if (errno == EAGAIN || errno == EWOULDBLOCK)
+                    state->again = 1;
                 gz_error(state, Z_ERRNO, zstrerror());
                 return -1;
             }
@@ -104,10 +108,14 @@ local int gz_comp(gz_statep state, int flush) {
         if (strm->avail_out == 0 || (flush != Z_NO_FLUSH &&
             (flush != Z_FINISH || ret == Z_STREAM_END))) {
             while (strm->next_out > state->x.next) {
+                errno = 0;
+                state->again = 0;
                 put = strm->next_out - state->x.next > (int)max ? max :
                       (unsigned)(strm->next_out - state->x.next);
                 writ = write(state->fd, state->x.next, put);
                 if (writ < 0) {
+                    if (errno == EAGAIN || errno == EWOULDBLOCK)
+                        state->again = 1;
                     gz_error(state, Z_ERRNO, zstrerror());
                     return -1;
                 }
@@ -140,9 +148,11 @@ local int gz_comp(gz_statep state, int flush) {
 }

 /* Compress state->skip (> 0) zeros to output.  Return -1 on a write error or
-   memory allocation failure by gz_comp(), or 0 on success. */
+   memory allocation failure by gz_comp(), or 0 on success. state->skip is
+   updated with the number of successfully written zeros, in case there is a
+   stall on a non-blocking write destination. */
 local int gz_zero(gz_statep state) {
-    int first;
+    int first, ret;
     unsigned n;
     z_streamp strm = &(state->strm);

@@ -161,18 +171,23 @@ local int gz_zero(gz_statep state) {
         }
         strm->avail_in = n;
         strm->next_in = state->in;
+        ret = gz_comp(state, Z_NO_FLUSH);
+        n -= strm->avail_in;
         state->x.pos += n;
-        if (gz_comp(state, Z_NO_FLUSH) == -1)
-            return -1;
         state->skip -= n;
+        if (ret == -1)
+            return -1;
     } while (state->skip);
     return 0;
 }

 /* Write len bytes from buf to file.  Return the number of bytes written.  If
-   the returned value is less than len, then there was an error. */
+   the returned value is less than len, then there was an error. If the error
+   was a non-blocking stall, then the number of bytes consumed is returned.
+   For any other error, 0 is returned. */
 local z_size_t gz_write(gz_statep state, voidpc buf, z_size_t len) {
     z_size_t put = len;
+    int ret;

     /* if len is zero, avoid unnecessary operations */
     if (len == 0)
@@ -189,7 +204,7 @@ local z_size_t gz_write(gz_statep state, voidpc buf, z_size_t len) {
     /* for small len, copy to input buffer, otherwise compress directly */
     if (len < state->size) {
         /* copy to input buffer, compress when full */
-        do {
+        for (;;) {
             unsigned have, copy;

             if (state->strm.avail_in == 0)
@@ -204,9 +219,11 @@ local z_size_t gz_write(gz_statep state, voidpc buf, z_size_t len) {
             state->x.pos += copy;
             buf = (const char *)buf + copy;
             len -= copy;
-            if (len && gz_comp(state, Z_NO_FLUSH) == -1)
-                return 0;
-        } while (len);
+            if (len == 0)
+                break;
+            if (gz_comp(state, Z_NO_FLUSH) == -1)
+                return state->again ? put - len : 0;
+        }
     }
     else {
         /* consume whatever's left in the input buffer */
@@ -217,13 +234,16 @@ local z_size_t gz_write(gz_statep state, voidpc buf, z_size_t len) {
         state->strm.next_in = (z_const Bytef *)buf;
         do {
             unsigned n = (unsigned)-1;
+
             if (n > len)
                 n = (unsigned)len;
             state->strm.avail_in = n;
+            ret = gz_comp(state, Z_NO_FLUSH);
+            n -= state->strm.avail_in;
             state->x.pos += n;
-            if (gz_comp(state, Z_NO_FLUSH) == -1)
-                return 0;
             len -= n;
+            if (ret == -1)
+                return state->again ? put - len : 0;
         } while (len);
     }

@@ -240,9 +260,10 @@ int ZEXPORT gzwrite(gzFile file, voidpc buf, unsigned len) {
         return 0;
     state = (gz_statep)file;

-    /* check that we're writing and that there's no error */
-    if (state->mode != GZ_WRITE || state->err != Z_OK)
+    /* check that we're writing and that there's no (serious) error */
+    if (state->mode != GZ_WRITE || (state->err != Z_OK && !state->again))
         return 0;
+    gz_error(state, Z_OK, NULL);

     /* since an int is returned, make sure len fits in one, otherwise return
        with an error (this avoids a flaw in the interface) */
@@ -266,9 +287,10 @@ z_size_t ZEXPORT gzfwrite(voidpc buf, z_size_t size, z_size_t nitems,
         return 0;
     state = (gz_statep)file;

-    /* check that we're writing and that there's no error */
-    if (state->mode != GZ_WRITE || state->err != Z_OK)
+    /* check that we're writing and that there's no (serious) error */
+    if (state->mode != GZ_WRITE || (state->err != Z_OK && !state->again))
         return 0;
+    gz_error(state, Z_OK, NULL);

     /* compute bytes to read -- error on overflow */
     len = nitems * size;
@@ -294,9 +316,10 @@ int ZEXPORT gzputc(gzFile file, int c) {
     state = (gz_statep)file;
     strm = &(state->strm);

-    /* check that we're writing and that there's no error */
-    if (state->mode != GZ_WRITE || state->err != Z_OK)
+    /* check that we're writing and that there's no (serious) error */
+    if (state->mode != GZ_WRITE || (state->err != Z_OK && !state->again))
         return -1;
+    gz_error(state, Z_OK, NULL);

     /* check for seek request */
     if (state->skip && gz_zero(state) == -1)
@@ -333,9 +356,10 @@ int ZEXPORT gzputs(gzFile file, const char *s) {
         return -1;
     state = (gz_statep)file;

-    /* check that we're writing and that there's no error */
-    if (state->mode != GZ_WRITE || state->err != Z_OK)
+    /* check that we're writing and that there's no (serious) error */
+    if (state->mode != GZ_WRITE || (state->err != Z_OK && !state->again))
         return -1;
+    gz_error(state, Z_OK, NULL);

     /* write string */
     len = strlen(s);
@@ -344,7 +368,28 @@ int ZEXPORT gzputs(gzFile file, const char *s) {
         return -1;
     }
     put = gz_write(state, s, len);
-    return put < len ? -1 : (int)len;
+    return len && put == 0 ? -1 : (int)put;
+}
+
+/* If the second half of the input buffer is occupied, write out the contents.
+   If there is any input remaining due to a non-blocking stall on write, move
+   it to the start of the buffer. Return true if this did not open up the
+   second half of the buffer.  state->err should be checked after this to
+   handle a gz_comp() error. */
+local int gz_vacate(gz_statep state) {
+    z_streamp strm;
+
+    strm = &(state->strm);
+    if ((strm->next_in + strm->avail_in) - state->in <= state->size)
+        return 0;
+    (void)gz_comp(state, Z_NO_FLUSH);
+    if (strm->avail_in == 0) {
+        strm->next_in = state->in;
+        return 0;
+    }
+    memmove(state->in, strm->next_in, strm->avail_in);
+    strm->next_in = state->in;
+    return strm->avail_in > state->size;
 }

 #if defined(STDC) || defined(Z_HAVE_STDARG_H)
@@ -352,8 +397,7 @@ int ZEXPORT gzputs(gzFile file, const char *s) {

 /* -- see zlib.h -- */
 int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) {
-    int len;
-    unsigned left;
+    int len, ret;
     char *next;
     gz_statep state;
     z_streamp strm;
@@ -364,9 +408,10 @@ int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) {
     state = (gz_statep)file;
     strm = &(state->strm);

-    /* check that we're writing and that there's no error */
-    if (state->mode != GZ_WRITE || state->err != Z_OK)
+    /* check that we're writing and that there's no (serious) error */
+    if (state->mode != GZ_WRITE || (state->err != Z_OK && !state->again))
         return Z_STREAM_ERROR;
+    gz_error(state, Z_OK, NULL);

     /* make sure we have some buffer space */
     if (state->size == 0 && gz_init(state) == -1)
@@ -377,8 +422,20 @@ int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) {
         return state->err;

     /* do the printf() into the input buffer, put length in len -- the input
-       buffer is double-sized just for this function, so there is guaranteed to
-       be state->size bytes available after the current contents */
+       buffer is double-sized just for this function, so there should be
+       state->size bytes available after the current contents */
+    ret = gz_vacate(state);
+    if (state->err) {
+        if (ret && state->again) {
+            /* There was a non-blocking stall on write, resulting in the part
+               of the second half of the output buffer being occupied.  Return
+               a Z_BUF_ERROR to let the application know that this gzprintf()
+               needs to be retried. */
+            gz_error(state, Z_BUF_ERROR, "stalled write on gzprintf");
+        }
+        if (!state->again)
+            return state->err;
+    }
     if (strm->avail_in == 0)
         strm->next_in = state->in;
     next = (char *)(state->in + (strm->next_in - state->in) + strm->avail_in);
@@ -404,18 +461,14 @@ int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) {
     if (len == 0 || (unsigned)len >= state->size || next[state->size - 1] != 0)
         return 0;

-    /* update buffer and position, compress first half if past that */
+    /* update buffer and position */
     strm->avail_in += (unsigned)len;
     state->x.pos += len;
-    if (strm->avail_in >= state->size) {
-        left = strm->avail_in - state->size;
-        strm->avail_in = state->size;
-        if (gz_comp(state, Z_NO_FLUSH) == -1)
-            return state->err;
-        memmove(state->in, state->in + state->size, left);
-        strm->next_in = state->in;
-        strm->avail_in = left;
-    }
+
+    /* write out buffer if more than half is occupied */
+    ret = gz_vacate(state);
+    if (state->err && !state->again)
+        return state->err;
     return len;
 }

@@ -451,9 +504,10 @@ int ZEXPORTVA gzprintf(gzFile file, const char *format, int a1, int a2, int a3,
     if (sizeof(int) != sizeof(void *))
         return Z_STREAM_ERROR;

-    /* check that we're writing and that there's no error */
-    if (state->mode != GZ_WRITE || state->err != Z_OK)
+    /* check that we're writing and that there's no (serious) error */
+    if (state->mode != GZ_WRITE || (state->err != Z_OK && !state->again))
         return Z_STREAM_ERROR;
+    gz_error(state, Z_OK, NULL);

     /* make sure we have some buffer space */
     if (state->size == 0 && gz_init(state) == -1)
@@ -466,6 +520,18 @@ int ZEXPORTVA gzprintf(gzFile file, const char *format, int a1, int a2, int a3,
     /* do the printf() into the input buffer, put length in len -- the input
        buffer is double-sized just for this function, so there is guaranteed to
        be state->size bytes available after the current contents */
+    ret = gz_vacate(state);
+    if (state->err) {
+        if (ret && state->again) {
+            /* There was a non-blocking stall on write, resulting in the part
+               of the second half of the output buffer being occupied.  Return
+               a Z_BUF_ERROR to let the application know that this gzprintf()
+               needs to be retried. */
+            gz_error(state, Z_BUF_ERROR, "stalled write on gzprintf");
+        }
+        if (!state->again)
+            return state->err;
+    }
     if (strm->avail_in == 0)
         strm->next_in = state->in;
     next = (char *)(strm->next_in + strm->avail_in);
@@ -499,15 +565,11 @@ int ZEXPORTVA gzprintf(gzFile file, const char *format, int a1, int a2, int a3,
     /* update buffer and position, compress first half if past that */
     strm->avail_in += len;
     state->x.pos += len;
-    if (strm->avail_in >= state->size) {
-        left = strm->avail_in - state->size;
-        strm->avail_in = state->size;
-        if (gz_comp(state, Z_NO_FLUSH) == -1)
-            return state->err;
-        memmove(state->in, state->in + state->size, left);
-        strm->next_in = state->in;
-        strm->avail_in = left;
-    }
+
+    /* write out buffer if more than half is occupied */
+    ret = gz_vacate(state);
+    if (state->err && !state->again)
+        return state->err;
     return (int)len;
 }

@@ -522,9 +584,10 @@ int ZEXPORT gzflush(gzFile file, int flush) {
         return Z_STREAM_ERROR;
     state = (gz_statep)file;

-    /* check that we're writing and that there's no error */
-    if (state->mode != GZ_WRITE || state->err != Z_OK)
+    /* check that we're writing and that there's no (serious) error */
+    if (state->mode != GZ_WRITE || (state->err != Z_OK && !state->again))
         return Z_STREAM_ERROR;
+    gz_error(state, Z_OK, NULL);

     /* check flush parameter */
     if (flush < 0 || flush > Z_FINISH)
@@ -550,9 +613,11 @@ int ZEXPORT gzsetparams(gzFile file, int level, int strategy) {
     state = (gz_statep)file;
     strm = &(state->strm);

-    /* check that we're writing and that there's no error */
-    if (state->mode != GZ_WRITE || state->err != Z_OK || state->direct)
+    /* check that we're compressing and that there's no (serious) error */
+    if (state->mode != GZ_WRITE || (state->err != Z_OK && !state->again) ||
+            state->direct)
         return Z_STREAM_ERROR;
+    gz_error(state, Z_OK, NULL);

     /* if no change is requested, then do nothing */
     if (level == state->level && strategy == state->strategy)
diff --git a/zlib.h b/zlib.h
index fa8b3d4..842caa8 100644
--- a/zlib.h
+++ b/zlib.h
@@ -1349,13 +1349,13 @@ ZEXTERN gzFile ZEXPORT gzopen(const char *path, const char *mode);
    used to force transparent reading. Transparent reading is automatically
    performed if there is no gzip header at the start. Transparent reading can
    be disabled with the 'G' option, which will instead return an error if there
-   is no gzip header.
+   is no gzip header. 'N' will open the file in non-blocking mode.

-     "a" can be used instead of "w" to request that the gzip stream that will
-   be written be appended to the file.  "+" will result in an error, since
+     'a' can be used instead of 'w' to request that the gzip stream that will
+   be written be appended to the file.  '+' will result in an error, since
    reading and writing to the same gzip file is not supported.  The addition of
-   "x" when writing will create the file exclusively, which fails if the file
-   already exists.  On systems that support it, the addition of "e" when
+   'x' when writing will create the file exclusively, which fails if the file
+   already exists.  On systems that support it, the addition of 'e' when
    reading or writing will set the flag to close the file on an execve() call.

      These functions, as well as gzip, will read and decode a sequence of gzip
@@ -1374,14 +1374,22 @@ ZEXTERN gzFile ZEXPORT gzopen(const char *path, const char *mode);
    insufficient memory to allocate the gzFile state, or if an invalid mode was
    specified (an 'r', 'w', or 'a' was not provided, or '+' was provided).
    errno can be checked to determine if the reason gzopen failed was that the
-   file could not be opened.
+   file could not be opened. Note that if 'N' is in mode for non-blocking, the
+   open() itself can fail in order to not block. In that case gzopen() will
+   return NULL and errno will be EAGAIN or ENONBLOCK. The call to gzopen() can
+   then be re-tried. If the application would like to block on opening the
+   file, then it can use open() without O_NONBLOCK, and then gzdopen() with the
+   resulting file descriptor and 'N' in the mode, which will set it to non-
+   blocking.
 */

 ZEXTERN gzFile ZEXPORT gzdopen(int fd, const char *mode);
 /*
      Associate a gzFile with the file descriptor fd.  File descriptors are
    obtained from calls like open, dup, creat, pipe or fileno (if the file has
-   been previously opened with fopen).  The mode parameter is as in gzopen.
+   been previously opened with fopen).  The mode parameter is as in gzopen. An
+   'e' in mode will set fd's flag to close the file on an execve() call. An 'N'
+   in mode will set fd's non-blocking flag.

      The next call of gzclose on the returned gzFile will also close the file
    descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor
@@ -1451,6 +1459,11 @@ ZEXTERN int ZEXPORT gzread(gzFile file, voidp buf, unsigned len);
    stream.  Alternatively, gzerror can be used before gzclose to detect this
    case.

+     gzread can be used to read a gzip file on a non-blocking device. If the
+   input stalls and there is no uncompressed data to return, then gzread() will
+   return -1, and errno will be EAGAIN or EWOULDBLOCK. gzread() can then be
+   called again.
+
      gzread returns the number of uncompressed bytes actually read, less than
    len for end of file, or -1 for error.  If len is too large to fit in an int,
    then nothing is read, -1 is returned, and the error state is set to
@@ -1479,15 +1492,20 @@ ZEXTERN z_size_t ZEXPORT gzfread(voidp buf, z_size_t size, z_size_t nitems,
    multiple of size, then the final partial item is nevertheless read into buf
    and the end-of-file flag is set.  The length of the partial item read is not
    provided, but could be inferred from the result of gztell().  This behavior
-   is the same as the behavior of fread() implementations in common libraries,
-   but it prevents the direct use of gzfread() to read a concurrently written
-   file, resetting and retrying on end-of-file, when size is not 1.
+   is the same as that of fread() implementations in common libraries. This
+   could result in data loss if used with size != 1 when reading a concurrently
+   written file or a non-blocking file. In that case, use size == 1 or gzread()
+   instead.
 */

 ZEXTERN int ZEXPORT gzwrite(gzFile file, voidpc buf, unsigned len);
 /*
      Compress and write the len uncompressed bytes at buf to file. gzwrite
-   returns the number of uncompressed bytes written or 0 in case of error.
+   returns the number of uncompressed bytes written, or 0 in case of error or
+   if len is 0.  If the write destination is non-blocking, then gzwrite() may
+   return a number of bytes written that is not 0 and less than len.
+
+     If len does not fit in an int, then 0 is returned and nothing is written.
 */

 ZEXTERN z_size_t ZEXPORT gzfwrite(voidpc buf, z_size_t size,
@@ -1502,6 +1520,11 @@ ZEXTERN z_size_t ZEXPORT gzfwrite(voidpc buf, z_size_t size,
    if there was an error.  If the multiplication of size and nitems overflows,
    i.e. the product does not fit in a z_size_t, then nothing is written, zero
    is returned, and the error state is set to Z_STREAM_ERROR.
+
+     If writing a concurrently read file or a non-blocking file with size != 1,
+   a partial item could be written, with no way of knowing how much of it was
+   not written, resulting in data loss.  In that case, use size == 1 or
+   gzwrite() instead.
 */

 ZEXTERN int ZEXPORTVA gzprintf(gzFile file, const char *format, ...);
@@ -1517,6 +1540,9 @@ ZEXTERN int ZEXPORTVA gzprintf(gzFile file, const char *format, ...);
    zlib was compiled with the insecure functions sprintf() or vsprintf(),
    because the secure snprintf() or vsnprintf() functions were not available.
    This can be determined using zlibCompileFlags().
+
+     If a Z_BUF_ERROR is returned, then nothing was written due to a stall on
+   the non-blocking write destination.
 */

 ZEXTERN int ZEXPORT gzputs(gzFile file, const char *s);
@@ -1525,6 +1551,11 @@ ZEXTERN int ZEXPORT gzputs(gzFile file, const char *s);
    the terminating null character.

      gzputs returns the number of characters written, or -1 in case of error.
+   The number of characters written may be less than the length of the string
+   if the write destination is non-blocking.
+
+     If the length of the string does not fit in an int, then -1 is returned
+   and nothing is written.
 */

 ZEXTERN char * ZEXPORT gzgets(gzFile file, char *buf, int len);
@@ -1540,6 +1571,10 @@ ZEXTERN char * ZEXPORT gzgets(gzFile file, char *buf, int len);
    for end-of-file or in case of error. If some data was read before an error,
    then that data is returned until exhausted, after which the next call will
    return NULL to signal the error.
+
+     gzgets can be used on a file being concurrently written, and on a non-
+   blocking device, both as for gzread(). However lines may be broken in the
+   middle, leaving it up to the application to reassemble them as needed.
 */

 ZEXTERN int ZEXPORT gzputc(gzFile file, int c);
@@ -1550,14 +1585,19 @@ ZEXTERN int ZEXPORT gzputc(gzFile file, int c);

 ZEXTERN int ZEXPORT gzgetc(gzFile file);
 /*
-      Read and decompress one byte from file. gzgetc returns this byte or -1 in
+     Read and decompress one byte from file. gzgetc returns this byte or -1 in
    case of end of file or error. If some data was read before an error, then
    that data is returned until exhausted, after which the next call will return
    -1 to signal the error.

-      This is implemented as a macro for speed. As such, it does not do all of
+     This is implemented as a macro for speed. As such, it does not do all of
    the checking the other functions do. I.e. it does not check to see if file
    is NULL, nor whether the structure file points to has been clobbered or not.
+
+     gzgetc can be used to read a gzip file on a non-blocking device. If the
+   input stalls and there is no uncompressed data to return, then gzgetc() will
+   return -1, and errno will be EAGAIN or EWOULDBLOCK. gzread() can then be
+   called again.
 */

 ZEXTERN int ZEXPORT gzungetc(int c, gzFile file);
@@ -1666,8 +1706,11 @@ ZEXTERN int ZEXPORT gzdirect(gzFile file);

      If gzdirect() is used immediately after gzopen() or gzdopen() it will
    cause buffers to be allocated to allow reading the file to determine if it
-   is a gzip file.  Therefore if gzbuffer() is used, it should be called before
-   gzdirect().
+   is a gzip file. Therefore if gzbuffer() is used, it should be called before
+   gzdirect(). If the input is being written concurrently or the device is non-
+   blocking, then gzdirect() may give a different answer once four bytes of
+   input have been accumulated, which is what is needed to confirm or deny a
+   gzip header. Before this, gzdirect() will return true (1).

      When writing, gzdirect() returns true (1) if transparent writing was
    requested ("wT" for the gzopen() mode), or false (0) otherwise.  (Note: