os400: implement character encoding conversion support
diff --git a/Makefile.am b/Makefile.am
index 8bc8afe..761733e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -34,9 +34,11 @@
OS400FILES = os400/README400 os400/initscript.sh os400/make.sh \
os400/make-src.sh os400/make-rpg.sh os400/make-include.sh \
-os400/os400sys.c os400/libssh2_config.h os400/macros.h \
+os400/os400sys.c os400/ccsid.c \
+os400/libssh2_config.h os400/macros.h os400/libssh2_ccsid.h \
os400/include/alloca.h os400/include/sys/socket.h os400/include/stdio.h \
os400/libssh2rpg/libssh2.rpgle.in \
+os400/libssh2rpg/libssh2_ccsid.rpgle.in \
os400/libssh2rpg/libssh2_publickey.rpgle \
os400/libssh2rpg/libssh2_sftp.rpgle \
Makefile.os400qc3.inc
diff --git a/os400/README400 b/os400/README400
index 2a3f5f7..fdac7da 100644
--- a/os400/README400
+++ b/os400/README400
@@ -13,9 +13,10 @@
QADRT does not define ASCII wrappers for all C/system procedures: an
additional module (os400sys.c) define some more of them, that are used by
libssh2 and that QADRT left out.
- Presently, there is no EBCDIC support wrappers provided. It is the
-responsibility of the caller to convert procedure string values and arguments
-between ASCII and EBCDIC.
+ Since standard library entry points expect and return ASCII character strings,
+additional procedures are provided for string transcoding (see below). No
+wrappers to standard procedures are provided: however, nested calls to
+transcoding procedures may be used.
Crypto API is provided by the IBM QC3 API library. It supports RSA, but not DSA.
@@ -89,6 +90,62 @@
+String transcoding support:
+
+ To help passing arbitrarily encoded string arguments and/or receiving string
+values from/to the libssh2 API, three non-standard additional procedures are
+provided. They use a session pointer and a "string cache" pointer.
+ Each time a string is transcoded, it is cached in the given cache. It is
+the responsibility of the caller to release the cache when its associted strings
+are no longer needed. These procedures and the string cache type are defined
+in a new libssh2_ccsid.h header file.
+ To create a string cache, use:
+
+#include <libssh2_ccsid.h>
+libssh2_string_cache * cache = NULL;
+
+ To release all strings in a cache, call:
+
+libssh2_release_string_cache(session, &cache);
+
+ The transcoding procedures are:
+
+char * libssh2_from_ccsid(LIBSSH2_SESSION *session,
+ libssh2_string_cache **cache,
+ unsigned short ccsid,
+ const char *string, ssize_t inlen,
+ size_t *outlen);
+char * libssh2_to_ccsid(LIBSSH2_SESSION *session,
+ libssh2_string_cache **cache,
+ unsigned short ccsid,
+ const char *string, ssize_t inlen,
+ size_t *outlen);
+
+where:
+ session is a libssh2 session used for memory allocation.
+ cache is the address of a string cache.
+ ccsid is the external (i.e.: non libssh2) coded character set id.
+ 65535 means no conversion and 0 means the current job's CCSID.
+ string is the string to convert.
+ inlen is the source string length in bytes: set to -1 if
+ null-terminated.
+ outlen if not NULL, is the address of a variable that will receive
+ the transcoded string length upon return.
+
+ libssh2_from_ccsid() transcodes the string from the given CCSID to libssh2
+internal encoding (UTF-8). It is intended to be used to convert API input
+parameters.
+ libssh2_to_ccsid() transcodes the string from libssh2 internal encoding
+(UTF-8) to the given CCSID. This has been implemented to get standard API
+string results in a program's native encoding.
+
+ Both these functions return a pointer to the null-terminated converted string,
+or NULL if an error occurred. In addition, the variable pointed by outlen
+receives the effective byte length of the (cached) translated string, or -1
+in case of error.
+
+
+
ILE/RPG support:
Since 95% of the OS/400 programmers use ILE/RPG exclusively, a definition
@@ -102,7 +159,7 @@
d/include libssh2/libssh2rpg,libssh2
in the global data section of the module's source code.
-If required, members ssh2_sftp and ssh2_pkey may also be included.
+If required, members ssh2_sftp, ssh2_pkey and ssh2_ccsid may also be included.
For IFS source compilations, include members are located in directory
/libssh2/include/libssh2rpg and have their original names retained.
diff --git a/os400/ccsid.c b/os400/ccsid.c
new file mode 100644
index 0000000..ef02f1d
--- /dev/null
+++ b/os400/ccsid.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2015 Patrick Monnerat, D+H <patrick.monnerat@dh.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * Neither the name of the copyright holder nor the names
+ * of any other contributors may be used to endorse or
+ * promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+/* Character encoding wrappers. */
+
+#include "libssh2_priv.h"
+#include "libssh2_ccsid.h"
+
+#include <qtqiconv.h>
+#include <iconv.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+
+
+#define CCSID_UTF8 1208
+#define CCSID_UTF16BE 13488
+#define STRING_GRANULE 256
+#define MAX_CHAR_SIZE 4
+
+#define OFFSET_OF(t, f) ((size_t) ((char *) &((t *) 0)->f - (char *) 0))
+
+
+struct _libssh2_string_cache {
+ libssh2_string_cache * next;
+ char string[1];
+};
+
+
+static const QtqCode_T utf8code = { CCSID_UTF8 };
+
+
+static ssize_t
+terminator_size(unsigned short ccsid)
+{
+ QtqCode_T outcode;
+ iconv_t cd;
+ char *inp;
+ char *outp;
+ size_t ilen;
+ size_t olen;
+ char buf[MAX_CHAR_SIZE];
+
+ /* Return the null-terminator size for the given CCSID. */
+
+ /* Fast check usual CCSIDs. */
+ switch (ccsid) {
+ case CCSID_UTF8:
+ case 0: /* Job CCSID is SBCS EBCDIC. */
+ return 1;
+ case CCSID_UTF16BE:
+ return 2;
+ }
+
+ /* Convert an UTF-8 NUL to the target CCSID: use the converted size as
+ result. */
+ memset((void *) &outcode, 0, sizeof outcode);
+ outcode.CCSID = ccsid;
+ cd = QtqIconvOpen(&outcode, (QtqCode_T *) &utf8code);
+ if (cd.return_value == -1)
+ return -1;
+ inp = "";
+ ilen = 1;
+ outp = buf;
+ olen = sizeof buf;
+ iconv(cd, &inp, &ilen, &outp, &olen);
+ iconv_close(cd);
+ olen = sizeof buf - olen;
+ return olen? olen: -1;
+}
+
+static char *
+convert_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+ unsigned short outccsid, unsigned short inccsid,
+ const char *instring, ssize_t inlen, size_t *outlen)
+{
+ char *inp;
+ char *outp;
+ size_t olen;
+ size_t ilen;
+ size_t buflen;
+ size_t curlen;
+ ssize_t termsize;
+ int i;
+ char *dst;
+ libssh2_string_cache *outstring;
+ QtqCode_T incode;
+ QtqCode_T outcode;
+ iconv_t cd;
+
+ if (!instring) {
+ if (outlen)
+ *outlen = 0;
+ return NULL;
+ }
+ if (outlen)
+ *outlen = -1;
+ if (!session || !cache)
+ return NULL;
+
+ /* Get terminator size. */
+ termsize = terminator_size(outccsid);
+ if (termsize < 0)
+ return NULL;
+
+ /* Prepare conversion parameters. */
+ memset((void *) &incode, 0, sizeof incode);
+ memset((void *) &outcode, 0, sizeof outcode);
+ incode.CCSID = inccsid;
+ outcode.CCSID = outccsid;
+ curlen = OFFSET_OF(libssh2_string_cache, string);
+ inp = (char *) instring;
+ ilen = inlen;
+ buflen = inlen + curlen;
+ if (inlen < 0) {
+ incode.length_option = 1;
+ buflen = STRING_GRANULE;
+ ilen = 0;
+ }
+
+ /* Allocate output string buffer and open conversion descriptor. */
+ dst = LIBSSH2_ALLOC(session, buflen + termsize);
+ if (!dst)
+ return NULL;
+ cd = QtqIconvOpen(&outcode, &incode);
+ if (cd.return_value == -1) {
+ LIBSSH2_FREE(session, (char *) dst);
+ return NULL;
+ }
+
+ /* Convert string. */
+ for (;;) {
+ outp = dst + curlen;
+ olen = buflen - curlen;
+ i = iconv(cd, &inp, &ilen, &outp, &olen);
+ if (inlen < 0 && olen == buflen - curlen) {
+ /* Special case: converted 0-length (sub)strings do not store the
+ terminator. */
+ if (termsize) {
+ memset(outp, 0, termsize);
+ olen -= termsize;
+ }
+ }
+ curlen = buflen - olen;
+ if (i >= 0 || errno != E2BIG)
+ break;
+ /* Must expand buffer. */
+ buflen += STRING_GRANULE;
+ outp = LIBSSH2_REALLOC(session, dst, buflen + termsize);
+ if (!outp)
+ break;
+ dst = outp;
+ }
+
+ iconv_close(cd);
+
+ /* Check for error. */
+ if (i < 0 || !outp) {
+ LIBSSH2_FREE(session, dst);
+ return NULL;
+ }
+
+ /* Process terminator. */
+ if (inlen < 0)
+ curlen -= termsize;
+ else if (termsize)
+ memset(dst + curlen, 0, termsize);
+
+ /* Shorten buffer if possible. */
+ if (curlen < buflen)
+ dst = LIBSSH2_REALLOC(session, dst, curlen + termsize);
+
+ /* Link to cache. */
+ outstring = (libssh2_string_cache *) dst;
+ outstring->next = *cache;
+ *cache = outstring;
+
+ /* Return length if required. */
+ if (outlen)
+ *outlen = curlen - OFFSET_OF(libssh2_string_cache, string);
+
+ return outstring->string;
+}
+
+LIBSSH2_API char *
+libssh2_from_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+ unsigned short ccsid, const char *string, ssize_t inlen,
+ size_t *outlen)
+{
+ return convert_ccsid(session, cache,
+ CCSID_UTF8, ccsid, string, inlen, outlen);
+}
+
+LIBSSH2_API char *
+libssh2_to_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+ unsigned short ccsid, const char *string, ssize_t inlen,
+ size_t *outlen)
+{
+ return convert_ccsid(session, cache,
+ ccsid, CCSID_UTF8, string, inlen, outlen);
+}
+
+LIBSSH2_API void
+libssh2_release_string_cache(LIBSSH2_SESSION *session,
+ libssh2_string_cache **cache)
+{
+ libssh2_string_cache *p;
+
+ if (session && cache)
+ while ((p = *cache)) {
+ *cache = p->next;
+ LIBSSH2_FREE(session, (char *) p);
+ }
+}
+
+/* vim: set expandtab ts=4 sw=4: */
diff --git a/os400/libssh2_ccsid.h b/os400/libssh2_ccsid.h
new file mode 100644
index 0000000..632effa
--- /dev/null
+++ b/os400/libssh2_ccsid.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Patrick Monnerat, D+H <patrick.monnerat@dh.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * Neither the name of the copyright holder nor the names
+ * of any other contributors may be used to endorse or
+ * promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+/* CCSID conversion support. */
+
+#ifndef LIBSSH2_CCSID_H_
+#define LIBSSH2_CCSID_H_
+
+#include "libssh2.h"
+
+typedef struct _libssh2_string_cache libssh2_string_cache;
+
+
+LIBSSH2_API char *
+libssh2_from_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+ unsigned short ccsid, const char *string, ssize_t inlen,
+ size_t *outlen);
+LIBSSH2_API char *
+libssh2_to_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+ unsigned short ccsid, const char *string, ssize_t inlen,
+ size_t *outlen);
+LIBSSH2_API void
+libssh2_release_string_cache(LIBSSH2_SESSION *session,
+ libssh2_string_cache **cache);
+
+#endif
+
+/* vim: set expandtab ts=4 sw=4: */
diff --git a/os400/libssh2rpg/libssh2_ccsid.rpgle.in b/os400/libssh2rpg/libssh2_ccsid.rpgle.in
new file mode 100644
index 0000000..bdc6f93
--- /dev/null
+++ b/os400/libssh2rpg/libssh2_ccsid.rpgle.in
@@ -0,0 +1,69 @@
+ * Copyright (c) 2015 Patrick Monnerat, D+H <patrick.monnerat@dh.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * Neither the name of the copyright holder nor the names
+ * of any other contributors may be used to endorse or
+ * promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+
+ /if not defined(LIBSSH2_CCSID_H_)
+ /define LIBSSH2_CCSID_H_
+
+ /include "libssh2rpg/libssh2"
+
+ d libssh2_from_ccsid...
+ d pr * extproc('libssh2_from_ccsid') char *
+ d session * value LIBSSH2_SESSION *
+ d cache * value libssh2_string_cache
+ d *(*)
+ d ccsid value like(libssh2_Cushort)
+ d string * value options(*string) const char *
+ d inlen value like(libssh2_Cssize_t)
+ d outlen like(libssh2_Csize_t) options(*omit)
+
+ d libssh2_to_ccsid...
+ d pr * extproc('libssh2_to_ccsid') char *
+ d session * value LIBSSH2_SESSION *
+ d cache * libssh2_string_cache
+ d *(*)
+ d ccsid value like(libssh2_Cushort)
+ d string * value options(*string) const char *
+ d inlen value like(libssh2_Cssize_t)
+ d outlen like(libssh2_Csize_t) options(*omit)
+
+ d libssh2_release_string_cache...
+ d pr extproc(
+ d 'libssh2_release_string_cache')
+ d session * value LIBSSH2_SESSION *
+ d cache * libssh2_string_cache
+ d *(*)
+
+ /endif LIBSSH2_CCSID_H_
diff --git a/os400/make-include.sh b/os400/make-include.sh
index d44980f..2cb7b72 100644
--- a/os400/make-include.sh
+++ b/os400/make-include.sh
@@ -44,7 +44,7 @@
# Copy the header files.
-for HFILE in *.h
+for HFILE in *.h "${TOPDIR}/os400/libssh2_ccsid.h"
do DEST="${SRCPF}/`db2_name \"${HFILE}\"`.MBR"
if action_needed "${DEST}" "${HFILE}"
diff --git a/os400/make-src.sh b/os400/make-src.sh
index dd7d364..2e9a3c4 100644
--- a/os400/make-src.sh
+++ b/os400/make-src.sh
@@ -97,7 +97,8 @@
INCLUDES="'`pwd`'"
-for SRC in "${SCRIPTDIR}/os400sys.c" ${CSOURCES} ${CRYPTO_CSOURCES} macros.c
+for SRC in "${TOPDIR}/os400/os400sys.c" "${TOPDIR}/os400/ccsid.c" \
+ ${CSOURCES} ${CRYPTO_CSOURCES} macros.c
do MODULE=`db2_name "${SRC}"`
make_module "${MODULE}" "${SRC}"
done
@@ -142,7 +143,8 @@
# Gather the list of symbols to export.
-EXPORTS=`cat "${TOPDIR}"/include/*.h "${TOPDIR}"/os400/macros.h |
+EXPORTS=`cat "${TOPDIR}"/include/*.h "${TOPDIR}/os400/macros.h" \
+ "${TOPDIR}/os400/libssh2_ccsid.h" |
extproto |
sed -e 's/(.*//;s/[^A-Za-z0-9_]/ /g;s/ *$//;s/^.* //'`
@@ -178,7 +180,7 @@
CMD="${CMD} BNDDIR(${TARGETLIB}/${STATBNDDIR}"
if [ "${WITH_ZLIB}" != 0 ]
then CMD="${CMD} ${ZLIB_LIB}/${ZLIB_BNDDIR}"
- liblist -a "${ZLIB_LIB}"
+ liblist -a "${ZLIB_LIB}"
fi
CMD="${CMD})"
CMD="${CMD} BNDSRVPGM(QADRTTS)"