printers CHANGE optimize printing to file descriptor without vdprintf()

Instead of repeated opening and closing duplicated FD (converted to a
FILE stream), convert it to a FILE stream just for the first time and
then change the printing method to FILE stream printing.
diff --git a/src/printer.c b/src/printer.c
index 653dca8..8f4a067 100644
--- a/src/printer.c
+++ b/src/printer.c
@@ -65,6 +65,7 @@
     char *msg = NULL, *aux;
     va_list ap;
 #ifndef HAVE_VDPRINTF
+    int fd;
     FILE *stream;
 #endif
 
@@ -74,14 +75,31 @@
     case LYOUT_FD:
 #ifdef HAVE_VDPRINTF
         count = vdprintf(out->method.fd, format, ap);
-#else
-        stream = fdopen(dup(out->method.fd), "a+");
-        if (stream) {
-            count = vfprintf(stream, format, ap);
-            fclose(stream);
-        }
-#endif
         break;
+#else
+        /* Without vdfprintf(), change the printing method to printing to a FILE stream.
+         * To preserve the original file descriptor, duplicate it and use it to open file stream.
+         * Due to a standalone LYOUT_FDSTREAM, ly*_print_fd() functions are supposed to detect the
+         * change and close the stream on their exit. */
+        fd = dup(out->method.fd);
+        if (fd < 0) {
+            LOGERR(NULL, LY_ESYS, "Unable to duplicate provided file descriptor (%d) for printing the output (%s).",
+                   out->method.fd, strerror(errno));
+            va_end(ap);
+            return LY_ESYS;
+        }
+        stream = fdopen(fd, "a");
+        if (!stream) {
+            LOGERR(NULL, LY_ESYS, "Unable to open provided file descriptor (%d) for printing the output (%s).",
+                   out->method.fd, strerror(errno));
+            va_end(ap);
+            return LY_ESYS;
+        }
+        out->method.f = stream;
+        out->type = LYOUT_FDSTREAM;
+#endif
+        /* fall through */
+    case LYOUT_FDSTREAM:
     case LYOUT_STREAM:
         count = vfprintf(out->method.f, format, ap);
         break;
@@ -95,7 +113,7 @@
                 out->method.mem.size = 0;
                 LOGMEM(NULL);
                 va_end(ap);
-                return -1;
+                return LY_EMEM;
             }
             out->method.mem.buf = aux;
             out->method.mem.size = out->method.mem.len + count + 1;
@@ -125,6 +143,7 @@
 ly_print_flush(struct lyout *out)
 {
     switch (out->type) {
+    case LYOUT_FDSTREAM:
     case LYOUT_STREAM:
         fflush(out->method.f);
         break;
@@ -176,6 +195,7 @@
     case LYOUT_FD:
         written = write(out->method.fd, buf, count);
         break;
+    case LYOUT_FDSTREAM:
     case LYOUT_STREAM:
         written =  fwrite(buf, sizeof *buf, count, out->method.f);
         break;
@@ -213,6 +233,7 @@
         out->method.mem.len += count;
         break;
     case LYOUT_FD:
+    case LYOUT_FDSTREAM:
     case LYOUT_STREAM:
     case LYOUT_CALLBACK:
         /* buffer the hole */
@@ -250,6 +271,7 @@
         memcpy(&out->method.mem.buf[position], buf, count);
         break;
     case LYOUT_FD:
+    case LYOUT_FDSTREAM:
     case LYOUT_STREAM:
     case LYOUT_CALLBACK:
         if (out->buf_len < position + count) {
@@ -347,6 +369,7 @@
 API LY_ERR
 lys_print_fd(int fd, const struct lys_module *module, LYS_OUTFORMAT format, int line_length, int options)
 {
+    LY_ERR ret;
     struct lyout out;
 
     LY_CHECK_ARG_RET(NULL, fd >= 0, module, LY_EINVAL);
@@ -356,7 +379,16 @@
     out.type = LYOUT_FD;
     out.method.fd = fd;
 
-    return lys_print_(&out, module, format, line_length, options);
+    ret = lys_print_(&out, module, format, line_length, options);
+
+    if (out.type == LYOUT_FDSTREAM) {
+        /* close temporary stream based on the given file descriptor */
+        fclose(out.method.f);
+        /* move the original file descriptor to the end of the output file */
+        lseek(fd, 0, SEEK_END);
+    }
+
+    return ret;
 }
 
 API LY_ERR
diff --git a/src/printer_internal.h b/src/printer_internal.h
index 4d61285..a05b612 100644
--- a/src/printer_internal.h
+++ b/src/printer_internal.h
@@ -20,6 +20,7 @@
 typedef enum LYOUT_TYPE {
     LYOUT_FD,          /**< file descriptor */
     LYOUT_STREAM,      /**< FILE stream */
+    LYOUT_FDSTREAM,    /**< FILE stream based on duplicated file descriptor */
     LYOUT_MEMORY,      /**< memory */
     LYOUT_CALLBACK     /**< print via provided callback */
 } LYOUT_TYPE;