10a4eca3c703299143faf935c65baf1216ddc2cc
[libguestfs.git] / generator / generator_python.ml
1 (* libguestfs
2  * Copyright (C) 2009-2011 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  *)
18
19 (* Please read generator/README first. *)
20
21 open Printf
22
23 open Generator_types
24 open Generator_utils
25 open Generator_pr
26 open Generator_docstrings
27 open Generator_optgroups
28 open Generator_actions
29 open Generator_structs
30 open Generator_c
31 open Generator_events
32
33 (* Generate Python C module. *)
34 let rec generate_python_c () =
35   generate_header CStyle LGPLv2plus;
36
37   pr "\
38 #include <config.h>
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <assert.h>
43
44 #include \"guestfs-py.h\"
45
46 /* This list should be freed (but not the strings) after use. */
47 static char **
48 get_string_list (PyObject *obj)
49 {
50   size_t i, len;
51   char **r;
52
53   assert (obj);
54
55   if (!PyList_Check (obj)) {
56     PyErr_SetString (PyExc_RuntimeError, \"expecting a list parameter\");
57     return NULL;
58   }
59
60   Py_ssize_t slen = PyList_Size (obj);
61   if (slen == -1) {
62     PyErr_SetString (PyExc_RuntimeError, \"get_string_list: PyList_Size failure\");
63     return NULL;
64   }
65   len = (size_t) slen;
66   r = malloc (sizeof (char *) * (len+1));
67   if (r == NULL) {
68     PyErr_SetString (PyExc_RuntimeError, \"get_string_list: out of memory\");
69     return NULL;
70   }
71
72   for (i = 0; i < len; ++i)
73     r[i] = PyString_AsString (PyList_GetItem (obj, i));
74   r[len] = NULL;
75
76   return r;
77 }
78
79 static PyObject *
80 put_string_list (char * const * const argv)
81 {
82   PyObject *list;
83   int argc, i;
84
85   for (argc = 0; argv[argc] != NULL; ++argc)
86     ;
87
88   list = PyList_New (argc);
89   for (i = 0; i < argc; ++i)
90     PyList_SetItem (list, i, PyString_FromString (argv[i]));
91
92   return list;
93 }
94
95 static PyObject *
96 put_table (char * const * const argv)
97 {
98   PyObject *list, *item;
99   int argc, i;
100
101   for (argc = 0; argv[argc] != NULL; ++argc)
102     ;
103
104   list = PyList_New (argc >> 1);
105   for (i = 0; i < argc; i += 2) {
106     item = PyTuple_New (2);
107     PyTuple_SetItem (item, 0, PyString_FromString (argv[i]));
108     PyTuple_SetItem (item, 1, PyString_FromString (argv[i+1]));
109     PyList_SetItem (list, i >> 1, item);
110   }
111
112   return list;
113 }
114
115 static void
116 free_strings (char **argv)
117 {
118   int argc;
119
120   for (argc = 0; argv[argc] != NULL; ++argc)
121     free (argv[argc]);
122   free (argv);
123 }
124
125 ";
126
127   let emit_put_list_function typ =
128     pr "static PyObject *\n";
129     pr "put_%s_list (struct guestfs_%s_list *%ss)\n" typ typ typ;
130     pr "{\n";
131     pr "  PyObject *list;\n";
132     pr "  size_t i;\n";
133     pr "\n";
134     pr "  list = PyList_New (%ss->len);\n" typ;
135     pr "  for (i = 0; i < %ss->len; ++i)\n" typ;
136     pr "    PyList_SetItem (list, i, put_%s (&%ss->val[i]));\n" typ typ;
137     pr "  return list;\n";
138     pr "};\n";
139     pr "\n"
140   in
141
142   (* Structures, turned into Python dictionaries. *)
143   List.iter (
144     fun (typ, cols) ->
145       pr "static PyObject *\n";
146       pr "put_%s (struct guestfs_%s *%s)\n" typ typ typ;
147       pr "{\n";
148       pr "  PyObject *dict;\n";
149       pr "\n";
150       pr "  dict = PyDict_New ();\n";
151       List.iter (
152         function
153         | name, FString ->
154             pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
155             pr "                        PyString_FromString (%s->%s));\n"
156               typ name
157         | name, FBuffer ->
158             pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
159             pr "                        PyString_FromStringAndSize (%s->%s, %s->%s_len));\n"
160               typ name typ name
161         | name, FUUID ->
162             pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
163             pr "                        PyString_FromStringAndSize (%s->%s, 32));\n"
164               typ name
165         | name, (FBytes|FUInt64) ->
166             pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
167             pr "                        PyLong_FromUnsignedLongLong (%s->%s));\n"
168               typ name
169         | name, FInt64 ->
170             pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
171             pr "                        PyLong_FromLongLong (%s->%s));\n"
172               typ name
173         | name, FUInt32 ->
174             pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
175             pr "                        PyLong_FromUnsignedLong (%s->%s));\n"
176               typ name
177         | name, FInt32 ->
178             pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
179             pr "                        PyLong_FromLong (%s->%s));\n"
180               typ name
181         | name, FOptPercent ->
182             pr "  if (%s->%s >= 0)\n" typ name;
183             pr "    PyDict_SetItemString (dict, \"%s\",\n" name;
184             pr "                          PyFloat_FromDouble ((double) %s->%s));\n"
185               typ name;
186             pr "  else {\n";
187             pr "    Py_INCREF (Py_None);\n";
188             pr "    PyDict_SetItemString (dict, \"%s\", Py_None);\n" name;
189             pr "  }\n"
190         | name, FChar ->
191             pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
192             pr "                        PyString_FromStringAndSize (&dirent->%s, 1));\n" name
193       ) cols;
194       pr "  return dict;\n";
195       pr "};\n";
196       pr "\n";
197
198   ) structs;
199
200   (* Emit a put_TYPE_list function definition only if that function is used. *)
201   List.iter (
202     function
203     | typ, (RStructListOnly | RStructAndList) ->
204         (* generate the function for typ *)
205         emit_put_list_function typ
206     | typ, _ -> () (* empty *)
207   ) (rstructs_used_by all_functions);
208
209   (* Python wrapper functions. *)
210   List.iter (
211     fun (name, (ret, args, optargs as style), _, _, _, _, _) ->
212       pr "static PyObject *\n";
213       pr "py_guestfs_%s (PyObject *self, PyObject *args)\n" name;
214       pr "{\n";
215
216       pr "  PyThreadState *py_save = NULL;\n";
217       pr "  PyObject *py_g;\n";
218       pr "  guestfs_h *g;\n";
219       pr "  PyObject *py_r;\n";
220
221       if optargs <> [] then (
222         pr "  struct guestfs_%s_argv optargs_s;\n" name;
223         pr "  struct guestfs_%s_argv *optargs = &optargs_s;\n" name;
224       );
225
226       (match ret with
227        | RErr | RInt _ | RBool _ -> pr "  int r;\n"
228        | RInt64 _ -> pr "  int64_t r;\n"
229        | RConstString _ | RConstOptString _ ->
230            pr "  const char *r;\n"
231        | RString _ -> pr "  char *r;\n"
232        | RStringList _ | RHashtable _ -> pr "  char **r;\n"
233        | RStruct (_, typ) -> pr "  struct guestfs_%s *r;\n" typ
234        | RStructList (_, typ) ->
235            pr "  struct guestfs_%s_list *r;\n" typ
236        | RBufferOut _ ->
237            pr "  char *r;\n";
238            pr "  size_t size;\n"
239       );
240
241       List.iter (
242         function
243         | Pathname n | Device n | Dev_or_Path n | String n | Key n
244         | FileIn n | FileOut n ->
245             pr "  const char *%s;\n" n
246         | OptString n -> pr "  const char *%s;\n" n
247         | BufferIn n ->
248             pr "  const char *%s;\n" n;
249             pr "  Py_ssize_t %s_size;\n" n
250         | StringList n | DeviceList n ->
251             pr "  PyObject *py_%s;\n" n;
252             pr "  char **%s;\n" n
253         | Bool n -> pr "  int %s;\n" n
254         | Int n -> pr "  int %s;\n" n
255         | Int64 n -> pr "  long long %s;\n" n
256         | Pointer (t, n) ->
257             pr "  long long %s_int64;\n" n;
258             pr "  %s %s;\n" t n
259       ) args;
260
261       if optargs <> [] then (
262         (* XXX This is horrible.  We have to use sentinel values on the
263          * Python side to denote values not set.
264          *)
265         (* Since we don't know if Python types will exactly match
266          * structure types, declare some local variables here.
267          *)
268         List.iter (
269           function
270           | Bool n
271           | Int n -> pr "  int optargs_t_%s = -1;\n" n
272           | Int64 n -> pr "  long long optargs_t_%s = -1;\n" n
273           | String n -> pr "  const char *optargs_t_%s = NULL;\n" n
274           | _ -> assert false
275         ) optargs
276       );
277
278       pr "\n";
279
280       if optargs <> [] then (
281         pr "  optargs_s.bitmask = 0;\n";
282         pr "\n"
283       );
284
285       (* Convert the required parameters. *)
286       pr "  if (!PyArg_ParseTuple (args, (char *) \"O";
287       List.iter (
288         function
289         | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
290         | FileIn _ | FileOut _ -> pr "s"
291         | OptString _ -> pr "z"
292         | StringList _ | DeviceList _ -> pr "O"
293         | Bool _ -> pr "i" (* XXX Python has booleans? *)
294         | Int _ -> pr "i"
295         | Int64 _ | Pointer _ ->
296             (* XXX Whoever thought it was a good idea to
297              * emulate C's int/long/long long in Python?
298              *)
299             pr "L"
300         | BufferIn _ -> pr "s#"
301       ) args;
302
303       (* Optional parameters. *)
304       if optargs <> [] then (
305         List.iter (
306           function
307           | Bool _ | Int _ -> pr "i"
308           | Int64 _ -> pr "L"
309           | String _ -> pr "z" (* because we use None to mean not set *)
310           | _ -> assert false
311         ) optargs;
312       );
313
314       pr ":guestfs_%s\",\n" name;
315       pr "                         &py_g";
316       List.iter (
317         function
318         | Pathname n | Device n | Dev_or_Path n | String n | Key n
319         | FileIn n | FileOut n -> pr ", &%s" n
320         | OptString n -> pr ", &%s" n
321         | StringList n | DeviceList n -> pr ", &py_%s" n
322         | Bool n -> pr ", &%s" n
323         | Int n -> pr ", &%s" n
324         | Int64 n -> pr ", &%s" n
325         | Pointer (_, n) -> pr ", &%s_int64" n
326         | BufferIn n -> pr ", &%s, &%s_size" n n
327       ) args;
328
329       List.iter (
330         function
331         | Bool n | Int n | Int64 n | String n -> pr ", &optargs_t_%s" n
332         | _ -> assert false
333       ) optargs;
334
335       pr "))\n";
336       pr "    return NULL;\n";
337
338       pr "  g = get_handle (py_g);\n";
339       List.iter (
340         function
341         | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
342         | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
343         | BufferIn _ -> ()
344         | StringList n | DeviceList n ->
345             pr "  %s = get_string_list (py_%s);\n" n n;
346             pr "  if (!%s) return NULL;\n" n
347         | Pointer (t, n) ->
348             pr "  %s = (%s) (intptr_t) %s_int64;\n" n t n
349       ) args;
350
351       pr "\n";
352
353       if optargs <> [] then (
354         let uc_name = String.uppercase name in
355         List.iter (
356           fun argt ->
357             let n = name_of_argt argt in
358             let uc_n = String.uppercase n in
359             pr "  if (optargs_t_%s != " n;
360             (match argt with
361              | Bool _ | Int _ | Int64 _ -> pr "-1"
362              | String _ -> pr "NULL"
363              | _ -> assert false
364             );
365             pr ") {\n";
366             pr "    optargs_s.%s = optargs_t_%s;\n" n n;
367             pr "    optargs_s.bitmask |= GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n;
368             pr "  }\n"
369         ) optargs;
370         pr "\n"
371       );
372
373       (* Release Python GIL while running.  This code is from
374        * libvirt/python/typewrappers.h.  Thanks to Dan Berrange for
375        * showing us how to do this properly.
376        *)
377       pr "  if (PyEval_ThreadsInitialized ())\n";
378       pr "    py_save = PyEval_SaveThread ();\n";
379       pr "\n";
380
381       if optargs = [] then
382         pr "  r = guestfs_%s " name
383       else
384         pr "  r = guestfs_%s_argv " name;
385       generate_c_call_args ~handle:"g" style;
386       pr ";\n";
387
388       pr "\n";
389       pr "  if (PyEval_ThreadsInitialized ())\n";
390       pr "    PyEval_RestoreThread (py_save);\n";
391       pr "\n";
392
393       List.iter (
394         function
395         | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
396         | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
397         | BufferIn _ | Pointer _ -> ()
398         | StringList n | DeviceList n ->
399             pr "  free (%s);\n" n
400       ) args;
401
402       (match errcode_of_ret ret with
403        | `CannotReturnError -> ()
404        | `ErrorIsMinusOne ->
405            pr "  if (r == -1) {\n";
406            pr "    PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g));\n";
407            pr "    return NULL;\n";
408            pr "  }\n"
409        | `ErrorIsNULL ->
410            pr "  if (r == NULL) {\n";
411            pr "    PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g));\n";
412            pr "    return NULL;\n";
413            pr "  }\n"
414       );
415       pr "\n";
416
417       (match ret with
418        | RErr ->
419            pr "  Py_INCREF (Py_None);\n";
420            pr "  py_r = Py_None;\n"
421        | RInt _
422        | RBool _ -> pr "  py_r = PyInt_FromLong ((long) r);\n"
423        | RInt64 _ -> pr "  py_r = PyLong_FromLongLong (r);\n"
424        | RConstString _ -> pr "  py_r = PyString_FromString (r);\n"
425        | RConstOptString _ ->
426            pr "  if (r)\n";
427            pr "    py_r = PyString_FromString (r);\n";
428            pr "  else {\n";
429            pr "    Py_INCREF (Py_None);\n";
430            pr "    py_r = Py_None;\n";
431            pr "  }\n"
432        | RString _ ->
433            pr "  py_r = PyString_FromString (r);\n";
434            pr "  free (r);\n"
435        | RStringList _ ->
436            pr "  py_r = put_string_list (r);\n";
437            pr "  free_strings (r);\n"
438        | RStruct (_, typ) ->
439            pr "  py_r = put_%s (r);\n" typ;
440            pr "  guestfs_free_%s (r);\n" typ
441        | RStructList (_, typ) ->
442            pr "  py_r = put_%s_list (r);\n" typ;
443            pr "  guestfs_free_%s_list (r);\n" typ
444        | RHashtable n ->
445            pr "  py_r = put_table (r);\n";
446            pr "  free_strings (r);\n"
447        | RBufferOut _ ->
448            pr "  py_r = PyString_FromStringAndSize (r, size);\n";
449            pr "  free (r);\n"
450       );
451
452       pr "  return py_r;\n";
453       pr "}\n";
454       pr "\n"
455   ) all_functions;
456
457   (* Table of functions. *)
458   pr "static PyMethodDef methods[] = {\n";
459   pr "  { (char *) \"create\", py_guestfs_create, METH_VARARGS, NULL },\n";
460   pr "  { (char *) \"close\", py_guestfs_close, METH_VARARGS, NULL },\n";
461   pr "  { (char *) \"set_event_callback\",\n";
462   pr "    py_guestfs_set_event_callback, METH_VARARGS, NULL },\n";
463   pr "  { (char *) \"delete_event_callback\",\n";
464   pr "    py_guestfs_delete_event_callback, METH_VARARGS, NULL },\n";
465   List.iter (
466     fun (name, _, _, _, _, _, _) ->
467       pr "  { (char *) \"%s\", py_guestfs_%s, METH_VARARGS, NULL },\n"
468         name name
469   ) all_functions;
470   pr "  { NULL, NULL, 0, NULL }\n";
471   pr "};\n";
472   pr "\n";
473
474   (* Init function. *)
475   pr "\
476 void
477 initlibguestfsmod (void)
478 {
479   static int initialized = 0;
480
481   if (initialized) return;
482   Py_InitModule ((char *) \"libguestfsmod\", methods);
483   initialized = 1;
484 }
485 "
486
487 (* Generate Python module. *)
488 and generate_python_py () =
489   generate_header HashStyle LGPLv2plus;
490
491   pr "\
492 u\"\"\"Python bindings for libguestfs
493
494 import guestfs
495 g = guestfs.GuestFS ()
496 g.add_drive_opts (\"guest.img\", format=\"raw\")
497 g.launch ()
498 parts = g.list_partitions ()
499
500 The guestfs module provides a Python binding to the libguestfs API
501 for examining and modifying virtual machine disk images.
502
503 Amongst the things this is good for: making batch configuration
504 changes to guests, getting disk used/free statistics (see also:
505 virt-df), migrating between virtualization systems (see also:
506 virt-p2v), performing partial backups, performing partial guest
507 clones, cloning guests and changing registry/UUID/hostname info, and
508 much else besides.
509
510 Libguestfs uses Linux kernel and qemu code, and can access any type of
511 guest filesystem that Linux and qemu can, including but not limited
512 to: ext2/3/4, btrfs, FAT and NTFS, LVM, many different disk partition
513 schemes, qcow, qcow2, vmdk.
514
515 Libguestfs provides ways to enumerate guest storage (eg. partitions,
516 LVs, what filesystem is in each LV, etc.).  It can also run commands
517 in the context of the guest.  Also you can access filesystems over
518 FUSE.
519
520 Errors which happen while using the API are turned into Python
521 RuntimeError exceptions.
522
523 To create a guestfs handle you usually have to perform the following
524 sequence of calls:
525
526 # Create the handle, call add_drive* at least once, and possibly
527 # several times if the guest has multiple block devices:
528 g = guestfs.GuestFS ()
529 g.add_drive_opts (\"guest.img\", format=\"raw\")
530
531 # Launch the qemu subprocess and wait for it to become ready:
532 g.launch ()
533
534 # Now you can issue commands, for example:
535 logvols = g.lvs ()
536
537 \"\"\"
538
539 import libguestfsmod
540
541 ";
542
543   List.iter (
544     fun (name, bitmask) ->
545       pr "EVENT_%s = 0x%x\n" (String.uppercase name) bitmask
546   ) events;
547   pr "\n";
548
549   pr "\
550 class ClosedHandle(ValueError):
551     pass
552
553 class GuestFS:
554     \"\"\"Instances of this class are libguestfs API handles.\"\"\"
555
556     def __init__ (self):
557         \"\"\"Create a new libguestfs handle.\"\"\"
558         self._o = libguestfsmod.create ()
559
560     def __del__ (self):
561         if self._o:
562             libguestfsmod.close (self._o)
563
564     def _check_not_closed (self):
565         if not self._o:
566             raise ClosedHandle (\"GuestFS: method called on closed handle\")
567
568     def close (self):
569         u\"\"\"Explicitly close the guestfs handle.
570
571         The handle is closed implicitly when its reference count goes
572         to zero (eg. when it goes out of scope or the program ends).
573
574         This call is only needed if you want to force the handle to
575         close now.  After calling this, the program must not call
576         any method on the handle (except the implicit call to
577         __del__ which happens when the final reference is cleaned up).
578         \"\"\"
579         self._check_not_closed ()
580         libguestfsmod.close (self._o)
581         self._o = None
582
583     def set_event_callback (self, cb, event_bitmask):
584         u\"\"\"Register an event callback.
585
586         Register \"cb\" as a callback function for all of the
587         events in \"event_bitmask\".  \"event_bitmask\" should be
588         one or more \"guestfs.EVENT_*\" flags logically or'd together.
589
590         This function returns an event handle which can be used
591         to delete the callback (see \"delete_event_callback\").
592
593         The callback function receives 4 parameters:
594
595         cb (event, event_handle, buf, array)
596
597         \"event\" is one of the \"EVENT_*\" flags.  \"buf\" is a
598         message buffer (only for some types of events).  \"array\"
599         is an array of integers (only for some types of events).
600
601         You should read the documentation for
602         \"guestfs_set_event_callback\" in guestfs(3) before using
603         this function.
604         \"\"\"
605         self._check_not_closed ()
606         return libguestfsmod.set_event_callback (self._o, cb, event_bitmask)
607
608     def delete_event_callback (self, event_handle):
609         u\"\"\"Delete an event callback.\"\"\"
610         self._check_not_closed ()
611         libguestfsmod.delete_event_callback (self._o, event_handle)
612
613 ";
614
615   List.iter (
616     fun (name, (ret, args, optargs), _, flags, _, _, longdesc) ->
617       pr "    def %s (self" name;
618       List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args;
619       List.iter (
620         function
621         | Bool n | Int n | Int64 n -> pr ", %s=-1" n
622         | String n -> pr ", %s=None" n
623         | _ -> assert false
624       ) optargs;
625       pr "):\n";
626
627       if not (List.mem NotInDocs flags) then (
628         let doc = replace_str longdesc "C<guestfs_" "C<g." in
629         let doc =
630           match ret with
631           | RErr | RInt _ | RInt64 _ | RBool _
632           | RConstOptString _ | RConstString _
633           | RString _ | RBufferOut _ -> doc
634           | RStringList _ ->
635               doc ^ "\n\nThis function returns a list of strings."
636           | RStruct (_, typ) ->
637               doc ^ sprintf "\n\nThis function returns a dictionary, with keys matching the various fields in the guestfs_%s structure." typ
638           | RStructList (_, typ) ->
639               doc ^ sprintf "\n\nThis function returns a list of %ss.  Each %s is represented as a dictionary." typ typ
640           | RHashtable _ ->
641               doc ^ "\n\nThis function returns a dictionary." in
642         let doc =
643           if List.mem ProtocolLimitWarning flags then
644             doc ^ "\n\n" ^ protocol_limit_warning
645           else doc in
646         let doc =
647           match deprecation_notice flags with
648           | None -> doc
649           | Some txt -> doc ^ "\n\n" ^ txt in
650         let doc = pod2text ~width:60 name doc in
651         let doc = List.map (fun line -> replace_str line "\\" "\\\\") doc in
652         let doc = String.concat "\n        " doc in
653         pr "        u\"\"\"%s\"\"\"\n" doc;
654       );
655       (* Callers might pass in iterables instead of plain lists;
656        * convert those to plain lists because the C side of things
657        * cannot deal with iterables.  (RHBZ#693306).
658        *)
659       List.iter (
660         function
661         | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _
662         | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _
663         | BufferIn _ | Pointer _ -> ()
664         | StringList n | DeviceList n ->
665             pr "        %s = list (%s)\n" n n
666       ) args;
667       pr "        self._check_not_closed ()\n";
668       pr "        return libguestfsmod.%s (self._o" name;
669       List.iter (fun arg -> pr ", %s" (name_of_argt arg)) (args@optargs);
670       pr ")\n\n";
671   ) all_functions