Support for Linux extended attributes.
authorRichard W.M. Jones <rjones@redhat.com>
Tue, 14 Jul 2009 10:50:55 +0000 (11:50 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Tue, 14 Jul 2009 12:32:16 +0000 (13:32 +0100)
This commit adds six calls to support Linux extended attributes.
They are:
  getxattrs     list all extended attributes for a file or directory
  setxattr      add/replace an extended attribute
  removexattr   remove an extended attribute
  lgetxattrs    \
  lsetxattr     (same as above, but operate on symbolic links)
  lremovexattr  /

See attr(5) for more information.

This also adds support for the FBuffer field type, which maps to
an XDR opaque<> or a C (int, char *) pair.

.gitignore
daemon/Makefile.am
daemon/configure.ac
daemon/xattr.c [new file with mode: 0644]
src/MAX_PROC_NR
src/generator.ml

index 4b56f55..87dab68 100644 (file)
@@ -96,6 +96,7 @@ java/com/redhat/et/libguestfs/Stat.java
 java/com/redhat/et/libguestfs/StatVFS.java
 java/com/redhat/et/libguestfs/Version.java
 java/com/redhat/et/libguestfs/VG.java
+java/com/redhat/et/libguestfs/XAttr.java
 java/doc-stamp
 java/Makefile.inc
 *.la
index 846a95c..7b8a87e 100644 (file)
@@ -65,6 +65,7 @@ guestfsd_SOURCES = \
        umask.c \
        upload.c \
        wc.c \
+       xattr.c \
        zero.c \
        zerofree.c \
        $(top_builddir)/../src/guestfs_protocol.h \
index 238532e..2fd1ae9 100644 (file)
@@ -63,7 +63,10 @@ AC_CHECK_LIB([portablexdr],[xdrmem_create],[],[
        ])
 
 dnl Functions which may not be available in older distributions.
-AC_CHECK_FUNCS([futimens])
+AC_CHECK_FUNCS([futimens listxattr llistxattr getxattr lgetxattr setxattr lsetxattr removexattr lremovexattr])
+
+dnl Headers.
+AC_CHECK_HEADERS([attr/xattr.h])
 
 dnl Produce output files.
 AC_CONFIG_HEADERS([config.h])
diff --git a/daemon/xattr.c b/daemon/xattr.c
new file mode 100644 (file)
index 0000000..76f44bb
--- /dev/null
@@ -0,0 +1,284 @@
+/* libguestfs - the guestfsd daemon
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <unistd.h>
+
+#ifdef HAVE_ATTR_XATTR_H
+#include <attr/xattr.h>
+
+#include "../src/guestfs_protocol.h"
+#include "daemon.h"
+#include "actions.h"
+
+static guestfs_int_xattr_list *getxattrs (char *path, ssize_t (*listxattr) (const char *path, char *list, size_t size), ssize_t (*getxattr) (const char *path, const char *name, void *value, size_t size));
+static int _setxattr (char *xattr, char *val, int vallen, char *path, int (*setxattr) (const char *path, const char *name, const void *value, size_t size, int flags));
+static int _removexattr (char *xattr, char *path, int (*removexattr) (const char *path, const char *name));
+
+guestfs_int_xattr_list *
+do_getxattrs (char *path)
+{
+#if defined(HAVE_LISTXATTR) && defined(HAVE_GETXATTR)
+  return getxattrs (path, listxattr, getxattr);
+#else
+  reply_with_error ("getxattrs: no support for listxattr and getxattr");
+  return NULL;
+#endif
+}
+
+guestfs_int_xattr_list *
+do_lgetxattrs (char *path)
+{
+#if defined(HAVE_LLISTXATTR) && defined(HAVE_LGETXATTR)
+  return getxattrs (path, llistxattr, lgetxattr);
+#else
+  reply_with_error ("lgetxattrs: no support for llistxattr and lgetxattr");
+  return NULL;
+#endif
+}
+
+int
+do_setxattr (char *xattr, char *val, int vallen, char *path)
+{
+#if defined(HAVE_SETXATTR)
+  return _setxattr (xattr, val, vallen, path, setxattr);
+#else
+  reply_with_error ("setxattr: no support for setxattr");
+  return -1;
+#endif
+}
+
+int
+do_lsetxattr (char *xattr, char *val, int vallen, char *path)
+{
+#if defined(HAVE_LSETXATTR)
+  return _setxattr (xattr, val, vallen, path, lsetxattr);
+#else
+  reply_with_error ("lsetxattr: no support for lsetxattr");
+  return -1;
+#endif
+}
+
+int
+do_removexattr (char *xattr, char *path)
+{
+#if defined(HAVE_REMOVEXATTR)
+  return _removexattr (xattr, path, removexattr);
+#else
+  reply_with_error ("removexattr: no support for removexattr");
+  return -1;
+#endif
+}
+
+int
+do_lremovexattr (char *xattr, char *path)
+{
+#if defined(HAVE_LREMOVEXATTR)
+  return _removexattr (xattr, path, lremovexattr);
+#else
+  reply_with_error ("lremovexattr: no support for lremovexattr");
+  return -1;
+#endif
+}
+
+static guestfs_int_xattr_list *
+getxattrs (char *path,
+          ssize_t (*listxattr) (const char *path, char *list, size_t size),
+          ssize_t (*getxattr) (const char *path, const char *name,
+                               void *value, size_t size))
+{
+  ssize_t len, vlen;
+  char *buf = NULL;
+  int i, j;
+  guestfs_int_xattr_list *r = NULL;
+
+  NEED_ROOT (NULL);
+  ABS_PATH (path, NULL);
+
+  CHROOT_IN;
+  len = listxattr (path, NULL, 0);
+  CHROOT_OUT;
+  if (len == -1) {
+    reply_with_perror ("listxattr");
+    goto error;
+  }
+
+  buf = malloc (len);
+  if (buf == NULL) {
+    reply_with_perror ("malloc");
+    goto error;
+  }
+
+  CHROOT_IN;
+  len = listxattr (path, buf, len);
+  CHROOT_OUT;
+  if (len == -1) {
+    reply_with_perror ("listxattr");
+    goto error;
+  }
+
+  r = calloc (1, sizeof (*r));
+  if (r == NULL) {
+    reply_with_perror ("malloc");
+    goto error;
+  }
+
+  /* What we get from the kernel is a string "foo\0bar\0baz" of length
+   * len.  First count the strings.
+   */
+  r->guestfs_int_xattr_list_len = 0;
+  for (i = 0; i < len; i += strlen (&buf[i]) + 1)
+    r->guestfs_int_xattr_list_len++;
+
+  r->guestfs_int_xattr_list_val =
+    calloc (r->guestfs_int_xattr_list_len, sizeof (guestfs_int_xattr));
+  if (r->guestfs_int_xattr_list_val == NULL) {
+    reply_with_perror ("calloc");
+    goto error;
+  }
+
+  for (i = 0, j = 0; i < len; i += strlen (&buf[i]) + 1, ++j) {
+    CHROOT_IN;
+    vlen = getxattr (path, &buf[i], NULL, 0);
+    CHROOT_OUT;
+    if (vlen == -1) {
+      reply_with_perror ("getxattr");
+      goto error;
+    }
+
+    r->guestfs_int_xattr_list_val[j].attrname = strdup (&buf[i]);
+    r->guestfs_int_xattr_list_val[j].attrval.attrval_val = malloc (vlen);
+    r->guestfs_int_xattr_list_val[j].attrval.attrval_len = vlen;
+
+    if (r->guestfs_int_xattr_list_val[j].attrname == NULL ||
+       r->guestfs_int_xattr_list_val[j].attrval.attrval_val == NULL) {
+      reply_with_perror ("malloc");
+      goto error;
+    }
+
+    CHROOT_IN;
+    vlen = getxattr (path, &buf[i],
+                    r->guestfs_int_xattr_list_val[j].attrval.attrval_val,
+                    vlen);
+    CHROOT_OUT;
+    if (vlen == -1) {
+      reply_with_perror ("getxattr");
+      goto error;
+    }
+  }
+
+  free (buf);
+
+  return r;
+
+ error:
+  free (buf);
+  if (r) {
+    if (r->guestfs_int_xattr_list_val)
+      for (i = 0; i < r->guestfs_int_xattr_list_len; ++i) {
+       free (r->guestfs_int_xattr_list_val[i].attrname);
+       free (r->guestfs_int_xattr_list_val[i].attrval.attrval_val);
+      }
+    free (r->guestfs_int_xattr_list_val);
+  }
+  free (r);
+  return NULL;
+}
+
+static int
+_setxattr (char *xattr, char *val, int vallen, char *path,
+          int (*setxattr) (const char *path, const char *name,
+                           const void *value, size_t size, int flags))
+{
+  int r;
+
+  CHROOT_IN;
+  r = setxattr (path, xattr, val, vallen, 0);
+  CHROOT_OUT;
+  if (r == -1) {
+    reply_with_perror ("setxattr");
+    return -1;
+  }
+
+  return 0;
+}
+
+static int
+_removexattr (char *xattr, char *path,
+             int (*removexattr) (const char *path, const char *name))
+{
+  int r;
+
+  CHROOT_IN;
+  r = removexattr (path, xattr);
+  CHROOT_OUT;
+  if (r == -1) {
+    reply_with_perror ("removexattr");
+    return -1;
+  }
+
+  return 0;
+}
+
+#else /* !HAVE_ATTR_XATTR_H */
+
+guestfs_int_xattr_list *
+do_getxattrs (char *path)
+{
+  reply_with_error ("getxattrs: no support for xattrs");
+  return NULL;
+}
+
+guestfs_int_xattr_list *
+do_lgetxattrs (char *path)
+{
+  reply_with_error ("lgetxattrs: no support for xattrs");
+  return NULL;
+}
+
+int
+do_setxattr (char *xattr, char *val, int vallen, char *path)
+{
+  reply_with_error ("setxattr: no support for xattrs");
+  return -1;
+}
+
+int
+do_lsetxattr (char *xattr, char *val, int vallen, char *path)
+{
+  reply_with_error ("lsetxattr: no support for xattrs");
+  return -1;
+}
+
+int
+do_removexattr (char *xattr, char *path)
+{
+  reply_with_error ("removexattr: no support for xattrs");
+  return -1;
+}
+
+int
+do_lremovexattr (char *xattr, char *path)
+{
+  reply_with_error ("lremovexattr: no support for xattrs");
+  return -1;
+}
+
+#endif /* !HAVE_ATTR_XATTR_H */
index dee261d..878d5a0 100644 (file)
@@ -1 +1 @@
-140
+146
index d3040d5..c163b3a 100755 (executable)
@@ -2868,6 +2868,65 @@ C<method> must be one of C<gzip>, C<compress> or C<bzip2>.
 
 See also: C<guestfs_file>");
 
+  ("getxattrs", (RStructList ("xattrs", "xattr"), [String "path"]), 141, [],
+   [],
+   "list extended attributes of a file or directory",
+   "\
+This call lists the extended attributes of the file or directory
+C<path>.
+
+At the system call level, this is a combination of the
+L<listxattr(2)> and L<getxattr(2)> calls.
+
+See also: C<guestfs_lgetxattrs>, L<attr(5)>.");
+
+  ("lgetxattrs", (RStructList ("xattrs", "xattr"), [String "path"]), 142, [],
+   [],
+   "list extended attributes of a file or directory",
+   "\
+This is the same as C<guestfs_getxattrs>, but if C<path>
+is a symbolic link, then it returns the extended attributes
+of the link itself.");
+
+  ("setxattr", (RErr, [String "xattr";
+                      String "val"; Int "vallen"; (* will be BufferIn *)
+                      String "path"]), 143, [],
+   [],
+   "set extended attribute of a file or directory",
+   "\
+This call sets the extended attribute named C<xattr>
+of the file C<path> to the value C<val> (of length C<vallen>).
+The value is arbitrary 8 bit data.
+
+See also: C<guestfs_lsetxattr>, L<attr(5)>.");
+
+  ("lsetxattr", (RErr, [String "xattr";
+                       String "val"; Int "vallen"; (* will be BufferIn *)
+                       String "path"]), 144, [],
+   [],
+   "set extended attribute of a file or directory",
+   "\
+This is the same as C<guestfs_setxattr>, but if C<path>
+is a symbolic link, then it sets an extended attribute
+of the link itself.");
+
+  ("removexattr", (RErr, [String "xattr"; String "path"]), 145, [],
+   [],
+   "remove extended attribute of a file or directory",
+   "\
+This call removes the extended attribute named C<xattr>
+of the file C<path>.
+
+See also: C<guestfs_lremovexattr>, L<attr(5)>.");
+
+  ("lremovexattr", (RErr, [String "xattr"; String "path"]), 146, [],
+   [],
+   "remove extended attribute of a file or directory",
+   "\
+This is the same as C<guestfs_removexattr>, but if C<path>
+is a symbolic link, then it removes an extended attribute
+of the link itself.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -2883,6 +2942,7 @@ let all_functions_sorted =
 type field =
   | FChar                      (* C 'char' (really, a 7 bit byte). *)
   | FString                    (* nul-terminated ASCII string. *)
+  | FBuffer                    (* opaque buffer of bytes, (char *, int) pair *)
   | FUInt32
   | FInt32
   | FUInt64
@@ -3021,6 +3081,12 @@ let structs = [
     "release", FInt64;
     "extra", FString;
   ];
+
+  (* Extended attribute. *)
+  "xattr", [
+    "attrname", FString;
+    "attrval", FBuffer;
+  ];
 ] (* end of structs *)
 
 (* Ugh, Java has to be different ..
@@ -3035,6 +3101,7 @@ let java_structs = [
   "statvfs", "StatVFS";
   "dirent", "Dirent";
   "version", "Version";
+  "xattr", "XAttr";
 ]
 
 (* Used for testing language bindings. *)
@@ -3448,6 +3515,10 @@ and generate_structs_pod () =
        | name, (FUInt64|FBytes) -> pr "   uint64_t %s;\n" name
        | name, FInt64 -> pr "   int64_t %s;\n" name
        | name, FString -> pr "   char *%s;\n" name
+       | name, FBuffer ->
+           pr "   /* The next two fields describe a byte array. */\n";
+           pr "   uint32_t %s_len;\n" name;
+           pr "   char *%s;\n" name
        | name, FUUID ->
            pr "   /* The next field is NOT nul-terminated, be careful when printing it: */\n";
            pr "   char %s[32];\n" name
@@ -3491,6 +3562,7 @@ and generate_xdr () =
        List.iter (function
                   | name, FChar -> pr "  char %s;\n" name
                   | name, FString -> pr "  string %s<>;\n" name
+                  | name, FBuffer -> pr "  opaque %s<>;\n" name
                   | name, FUUID -> pr "  opaque %s[32];\n" name
                   | name, (FInt32|FUInt32) -> pr "  int %s;\n" name
                   | name, (FInt64|FUInt64|FBytes) -> pr "  hyper %s;\n" name
@@ -3652,6 +3724,9 @@ and generate_structs_h () =
        function
        | name, FChar -> pr "  char %s;\n" name
        | name, FString -> pr "  char *%s;\n" name
+       | name, FBuffer ->
+           pr "  uint32_t %s_len;\n" name;
+           pr "  char *%s;\n" name
        | name, FUUID -> pr "  char %s[32]; /* this is NOT nul-terminated, be careful when printing */\n" name
        | name, FUInt32 -> pr "  uint32_t %s;\n" name
        | name, FInt32 -> pr "  int32_t %s;\n" name
@@ -4269,7 +4344,7 @@ and generate_daemon_actions () =
                 pr "    fprintf (stderr, \"%%s: failed to parse float '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
                 pr "    return -1;\n";
                 pr "  }\n";
-            | FInt32 | FUInt32 | FUInt64 | FChar ->
+            | FBuffer | FInt32 | FUInt32 | FUInt64 | FChar ->
                 assert false (* can never be an LVM column *)
            );
            pr "  tok = next;\n";
@@ -5052,6 +5127,7 @@ and generate_fish_cmds () =
   pr "#include <stdlib.h>\n";
   pr "#include <string.h>\n";
   pr "#include <inttypes.h>\n";
+  pr "#include <ctype.h>\n";
   pr "\n";
   pr "#include <guestfs.h>\n";
   pr "#include \"fish.h\"\n";
@@ -5129,7 +5205,7 @@ and generate_fish_cmds () =
   List.iter (
     fun (typ, cols) ->
       let needs_i =
-        List.exists (function (_, FUUID) -> true | _ -> false) cols in
+        List.exists (function (_, (FUUID|FBuffer)) -> true | _ -> false) cols in
 
       pr "static void print_%s (struct guestfs_%s *%s)\n" typ typ typ;
       pr "{\n";
@@ -5146,6 +5222,14 @@ and generate_fish_cmds () =
            pr "  for (i = 0; i < 32; ++i)\n";
            pr "    printf (\"%%c\", %s->%s[i]);\n" typ name;
            pr "  printf (\"\\n\");\n"
+       | name, FBuffer ->
+           pr "  printf (\"%s: \");\n" name;
+           pr "  for (i = 0; i < %s->%s_len; ++i)\n" typ name;
+           pr "    if (isprint (%s->%s[i]))\n" typ name;
+           pr "      printf (\"%%c\", %s->%s[i]);\n" typ name;
+           pr "    else\n";
+           pr "      printf (\"\\\\x%%02x\", %s->%s[i]);\n" typ name;
+           pr "  printf (\"\\n\");\n"
        | name, (FUInt64|FBytes) ->
            pr "  printf (\"%s: %%\" PRIu64 \"\\n\", %s->%s);\n" name typ name
        | name, FInt64 ->
@@ -5661,6 +5745,10 @@ copy_table (char * const * argv)
          (match col with
           | name, FString ->
               pr "  v = caml_copy_string (%s->%s);\n" typ name
+          | name, FBuffer ->
+              pr "  v = caml_alloc_string (%s->%s_len);\n" typ name;
+              pr "  memcpy (String_val (v), %s->%s, %s->%s_len);\n"
+                typ name typ name
           | name, FUUID ->
               pr "  v = caml_alloc_string (32);\n";
               pr "  memcpy (String_val (v), %s->%s, 32);\n" typ name
@@ -5841,6 +5929,7 @@ and generate_ocaml_structure_decls () =
       List.iter (
        function
        | name, FString -> pr "  %s : string;\n" name
+       | name, FBuffer -> pr "  %s : string;\n" name
        | name, FUUID -> pr "  %s : string;\n" name
        | name, (FBytes|FInt64|FUInt64) -> pr "  %s : int64;\n" name
        | name, (FInt32|FUInt32) -> pr "  %s : int32;\n" name
@@ -6137,6 +6226,9 @@ and generate_perl_struct_list_code typ cols name style n do_cleanups =
     | name, FUUID ->
        pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, 32), 0);\n"
          name (String.length name) n name
+    | name, FBuffer ->
+       pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, %s->val[i].%s_len), 0);\n"
+         name (String.length name) n name n name
     | name, (FBytes|FUInt64) ->
        pr "        (void) hv_store (hv, \"%s\", %d, my_newSVull (%s->val[i].%s), 0);\n"
          name (String.length name) n name
@@ -6176,6 +6268,9 @@ and generate_perl_struct_code typ cols name style n do_cleanups =
       | name, FString ->
          pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, 0)));\n"
            n name
+      | name, FBuffer ->
+         pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, %s->%s_len)));\n"
+           n name n name
       | name, FUUID ->
          pr "      PUSHs (sv_2mortal (newSVpv (%s->%s, 32)));\n"
            n name
@@ -6508,6 +6603,10 @@ py_guestfs_close (PyObject *self, PyObject *args)
            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
            pr "                        PyString_FromString (%s->%s));\n"
              typ name
+       | name, FBuffer ->
+           pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+           pr "                        PyString_FromStringAndSize (%s->%s, %s->%s_len));\n"
+             typ name typ name
        | name, FUUID ->
            pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
            pr "                        PyString_FromStringAndSize (%s->%s, 32));\n"
@@ -7051,6 +7150,8 @@ and generate_ruby_struct_code typ cols =
     function
     | name, FString ->
        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new2 (r->%s));\n" name name
+    | name, FBuffer ->
+       pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new (r->%s, r->%s_len));\n" name name name
     | name, FUUID ->
        pr "  rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new (r->%s, 32));\n" name name
     | name, (FBytes|FUInt64) ->
@@ -7079,6 +7180,8 @@ and generate_ruby_struct_list_code typ cols =
     function
     | name, FString ->
        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new2 (r->val[i].%s));\n" name name
+    | name, FBuffer ->
+       pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new (r->val[i].%s, r->val[i].%s_len));\n" name name name
     | name, FUUID ->
        pr "    rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new (r->val[i].%s, 32));\n" name name
     | name, (FBytes|FUInt64) ->
@@ -7290,7 +7393,8 @@ public class %s {
   List.iter (
     function
     | name, FString
-    | name, FUUID -> pr "  public String %s;\n" name
+    | name, FUUID
+    | name, FBuffer -> pr "  public String %s;\n" name
     | name, (FBytes|FUInt64|FInt64) -> pr "  public long %s;\n" name
     | name, (FUInt32|FInt32) -> pr "  public int %s;\n" name
     | name, FChar -> pr "  public char %s;\n" name
@@ -7554,6 +7658,15 @@ and generate_java_struct_return typ jtyp cols =
        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
        pr "    (*env)->SetObjectField (env, jr, fl, (*env)->NewStringUTF (env, s));\n";
        pr "  }\n";
+    | name, FBuffer ->
+       pr "  {\n";
+       pr "    int len = r->%s_len;\n" name;
+       pr "    char s[len+1];\n";
+       pr "    memcpy (s, r->%s, len);\n" name;
+       pr "    s[len] = 0;\n";
+       pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+       pr "    (*env)->SetObjectField (env, jr, fl, (*env)->NewStringUTF (env, s));\n";
+       pr "  }\n";
     | name, (FBytes|FUInt64|FInt64) ->
        pr "  fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n" name;
        pr "  (*env)->SetLongField (env, jr, fl, r->%s);\n" name;
@@ -7588,6 +7701,15 @@ and generate_java_struct_list_return typ jtyp cols =
        pr "      fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
        pr "      (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, s));\n";
        pr "    }\n";
+    | name, FBuffer ->
+       pr "    {\n";
+       pr "      int len = r->val[i].%s_len;\n" name;
+       pr "      char s[len+1];\n";
+       pr "      memcpy (s, r->val[i].%s, len);\n" name;
+       pr "      s[len] = 0;\n";
+       pr "      fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+       pr "      (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, s));\n";
+       pr "    }\n";
     | name, (FBytes|FUInt64|FInt64) ->
        pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n" name;
        pr "    (*env)->SetLongField (env, jfl, fl, r->val[i].%s);\n" name;