+
+/* Move any bytes in the overflow space to the start. This avoids any issue
+ * with a multibyte sequence that needs to span the end of the buffer. */
+static void wrap_overflow(xbuf *out, int siz)
+{
+ if (DEBUG_GTE(IO, 4))
+ rprintf(FINFO, "[%s] wrap-bytes moved: %d (iconvbufs)\n", who_am_i(), siz);
+ memcpy(out->buf, out->buf + out->size, siz);
+}
+
+/* This function converts the characters in the "in" xbuf into characters
+ * in the "out" xbuf. The "len" of the "in" xbuf is used starting from its
+ * "pos". The "size" of the "out" xbuf restricts how many characters can be
+ * stored, starting at its "pos+len" position. Note that the last byte of
+ * the buffer is never used, which reserves space for a terminating '\0'.
+ * If ICB_CIRCULAR_OUT is set, the output data can wrap around to the start,
+ * and the buf IS ASSUMED TO HAVE AN EXTRA 4 BYTES OF OVERFLOW SPACE at the
+ * end (the buffer will also not be expanded if it is already allocated).
+ * We return a 0 on success or a -1 on error. An error also sets errno to
+ * E2BIG, EILSEQ, or EINVAL (see below); otherwise errno will be set to 0.
+ * The "in" xbuf is altered to update "pos" and "len". The "out" xbuf has
+ * data appended, and its "len" incremented. If ICB_EXPAND_OUT is set in
+ * "flags", the "out" xbuf will also be allocated if empty, and expanded if
+ * too small (so E2BIG will not be returned). If ICB_INCLUDE_BAD is set in
+ * "flags", any badly-encoded chars are included verbatim in the "out" xbuf,
+ * so EILSEQ will not be returned. Likewise for ICB_INCLUDE_INCOMPLETE with
+ * respect to an incomplete multi-byte char at the end, which ensures that
+ * EINVAL is not returned. If ICB_INIT is set, the iconv() conversion state
+ * is initialized prior to processing the characters. */
+int iconvbufs(iconv_t ic, xbuf *in, xbuf *out, int flags)
+{
+ ICONV_CONST char *ibuf;
+ size_t icnt, ocnt, opos;
+ char *obuf;
+
+ if (!out->size && flags & ICB_EXPAND_OUT)
+ alloc_xbuf(out, 1024);
+
+ if (flags & ICB_INIT)
+ iconv(ic, NULL, 0, NULL, 0);
+
+ ibuf = in->buf + in->pos;
+ icnt = in->len;
+
+ opos = out->pos + out->len;
+ if (flags & ICB_CIRCULAR_OUT) {
+ if (opos >= out->size) {
+ opos -= out->size;
+ ocnt = out->pos - opos - 1;
+ } else {
+ /* We only make use of the 4 bytes of overflow buffer
+ * if there is room to move the bytes to the start of
+ * the circular buffer. */
+ ocnt = out->size - opos + MIN((ssize_t)out->pos - 1, 4);
+ }
+ } else
+ ocnt = out->size - opos - 1;
+ obuf = out->buf + opos;
+
+ while (icnt) {
+ while (iconv(ic, &ibuf, &icnt, &obuf, &ocnt) == (size_t)-1) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EINVAL) {
+ if (!(flags & ICB_INCLUDE_INCOMPLETE))
+ goto finish;
+ } else if (errno == EILSEQ) {
+ if (!(flags & ICB_INCLUDE_BAD))
+ goto finish;
+ } else {
+ opos = obuf - out->buf;
+ if (flags & ICB_CIRCULAR_OUT && opos > out->size) {
+ wrap_overflow(out, opos -= out->size);
+ obuf = out->buf + opos;
+ if ((ocnt = out->pos - opos - 1) > 0)
+ continue;
+ }
+ if (!(flags & ICB_EXPAND_OUT) || flags & ICB_CIRCULAR_OUT) {
+ errno = E2BIG;
+ goto finish;
+ }
+ realloc_xbuf(out, out->size + 1024);
+ obuf = out->buf + opos;
+ ocnt += 1024;
+ continue;
+ }
+ *obuf++ = *ibuf++;
+ ocnt--, icnt--;
+ }
+ }
+
+ errno = 0;
+
+ finish:
+ opos = obuf - out->buf;
+ if (flags & ICB_CIRCULAR_OUT) {
+ if (opos > out->size)
+ wrap_overflow(out, opos - out->size);
+ else if (opos < out->pos)
+ opos += out->size;
+ }
+ out->len = opos - out->pos;
+
+ in->len = icnt;
+ in->pos = ibuf - in->buf;
+
+ return errno ? -1 : 0;
+}
+#endif
+
+void send_protected_args(int fd, char *args[])
+{
+ int i;
+#ifdef ICONV_OPTION
+ int convert = ic_send != (iconv_t)-1;
+ xbuf outbuf, inbuf;
+
+ if (convert)
+ alloc_xbuf(&outbuf, 1024);
+#endif
+
+ for (i = 0; args[i]; i++) {} /* find first NULL */
+ args[i] = "rsync"; /* set a new arg0 */
+ if (DEBUG_GTE(CMD, 1))
+ print_child_argv("protected args:", args + i + 1);
+ do {
+ if (!args[i][0])
+ write_buf(fd, ".", 2);
+#ifdef ICONV_OPTION
+ else if (convert) {
+ INIT_XBUF_STRLEN(inbuf, args[i]);
+ iconvbufs(ic_send, &inbuf, &outbuf,
+ ICB_EXPAND_OUT | ICB_INCLUDE_BAD | ICB_INCLUDE_INCOMPLETE | ICB_INIT);
+ outbuf.buf[outbuf.len] = '\0';
+ write_buf(fd, outbuf.buf, outbuf.len + 1);
+ outbuf.len = 0;
+ }