NCBI C++ ToolKit
rpc.c
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* FreeTDS - Library of routines accessing Sybase and Microsoft databases
2  * Copyright (C) 1998, 1999, 2000, 2001 Brian Bruns
3  * Copyright (C) 2002, 2003, 2004, 2005 James K. Lowden
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20 
21 #include <config.h>
22 
23 #include <stdarg.h>
24 #include <stdio.h>
25 
26 #ifdef HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif /* HAVE_UNISTD_H */
29 
30 #ifdef HAVE_STDLIB_H
31 #include <stdlib.h>
32 #endif /* HAVE_STDLIB_H */
33 
34 #ifdef HAVE_STRING_H
35 #include <string.h>
36 #endif /* HAVE_STRING_H */
37 
38 #if HAVE_ERRNO_H
39 # include <errno.h>
40 #endif /* HAVE_ERRNO_H */
41 
42 #include <assert.h>
43 
44 #include <freetds/tds.h>
45 #include <freetds/convert.h>
46 #include <freetds/string.h>
47 #include <replacements.h>
48 #include <sybfront.h>
49 #include <sybdb.h>
50 #include <dblib.h>
51 
52 static void rpc_clear(DBREMOTE_PROC * rpc);
53 static void param_clear(DBREMOTE_PROC_PARAM * pparam);
54 
56 
57 /**
58  * \ingroup dblib_rpc
59  * \brief Initialize a remote procedure call.
60  *
61  * \param dbproc contains all information needed by db-lib to manage communications with the server.
62  * \param rpcname name of the stored procedure to be run.
63  * \param options Only supported option would be DBRPCRECOMPILE,
64  * which causes the stored procedure to be recompiled before executing.
65  * \remark The RPC functions are the only way to get back OUTPUT parameter data with db-lib
66  * from modern Microsoft servers.
67  * \retval SUCCEED normal.
68  * \retval FAIL on error
69  * \sa dbrpcparam(), dbrpcsend()
70  */
71 RETCODE
72 dbrpcinit(DBPROCESS * dbproc, const char rpcname[], DBSMALLINT options)
73 {
74  DBREMOTE_PROC **rpc;
75 
76  tdsdump_log(TDS_DBG_FUNC, "dbrpcinit(%p, %s, %d)\n", dbproc, rpcname, options);
78  CHECK_NULP(rpcname, "dbrpcinit", 2, FAIL);
79 
80  /*
81  * TODO: adhere to docs. Only Microsoft supports DBRPCRESET. They say:
82  * "Cancels a single stored procedure or a batch of stored procedures.
83  * If rpcname is specified, that new stored procedure is initialized after the cancellation is complete."
84  */
85  if (options & DBRPCRESET) {
87  dbproc->rpc = NULL;
88  return SUCCEED;
89  }
90 
91  /* any bits we want from the options argument */
92  /* dbrpcrecompile = options & DBRPCRECOMPILE; */
93  options &= ~DBRPCRECOMPILE; /* turn that one off, now that we've extracted it */
94 
95  /* all other options except DBRPCRECOMPILE are invalid */
96  DBPERROR_RETURN3(options, SYBEIPV, (int) options, "options", "dbrpcinit");
97 
98  /* find a free node */
99  for (rpc = &dbproc->rpc; *rpc != NULL; rpc = &(*rpc)->next) {
100  /* check existing nodes for name match (there shouldn't be one) */
101  if ((*rpc)->name == NULL || strcmp((*rpc)->name, rpcname) == 0) {
102  tdsdump_log(TDS_DBG_INFO1, "error: dbrpcinit called twice for procedure \"%s\"\n", rpcname);
103  return FAIL;
104  }
105  }
106 
107  /* rpc now contains the address of the dbproc's first empty (null) DBREMOTE_PROC* */
108 
109  /* allocate */
110  if ((*rpc = tds_new0(DBREMOTE_PROC, 1)) == NULL) {
111  dbperror(dbproc, SYBEMEM, errno);
112  return FAIL;
113  }
114 
115  if (((*rpc)->name = strdup(rpcname)) == NULL) {
116  free(*rpc);
117  *rpc = NULL;
118  dbperror(dbproc, SYBEMEM, errno);
119  return FAIL;
120  }
121 
122  /* store */
123  (*rpc)->options = options & DBRPCRECOMPILE;
124  (*rpc)->param_list = NULL;
125 
126  /* completed */
127  tdsdump_log(TDS_DBG_INFO1, "dbrpcinit() added rpcname \"%s\"\n", rpcname);
128 
129  return SUCCEED;
130 }
131 
132 /**
133  * \ingroup dblib_rpc
134  * \brief Add a parameter to a remote procedure call.
135  *
136  * Call between dbrpcinit() and dbrpcsend()
137  * \param dbproc contains all information needed by db-lib to manage communications with the server.
138  * \param paramname literal name of the parameter, according to the stored procedure (starts with '@'). Optional.
139  * If not used, parameters will be passed in order instead of by name.
140  * \param status must be DBRPCRETURN, if this parameter is a return parameter, else 0.
141  * \param type datatype of the value parameter e.g., SYBINT4, SYBCHAR.
142  * \param maxlen Maximum output size of the parameter's value to be returned by the stored procedure,
143  * usually the size of your host variable.
144  * Fixed-length datatypes take -1 (NULL or not).
145  * Non-OUTPUT parameters also use -1.
146  * Use 0 to send a NULL value for a variable length datatype.
147  * \param datalen For variable-length datatypes, the byte size of the data to be sent, exclusive of any null terminator.
148  * For fixed-length datatypes use -1. To send a NULL value, use 0.
149  * \param value Address of your host variable.
150  * \retval SUCCEED normal.
151  * \retval FAIL on error
152  * \sa dbrpcinit(), dbrpcsend()
153  */
154 RETCODE
155 dbrpcparam(DBPROCESS * dbproc, const char paramname[], BYTE status, int db_type, DBINT maxlen, DBINT datalen, BYTE * value)
156 {
157  char *name = NULL;
158  DBREMOTE_PROC *rpc;
159  DBREMOTE_PROC_PARAM **pparam;
160  DBREMOTE_PROC_PARAM *param;
162 
163  tdsdump_log(TDS_DBG_FUNC, "dbrpcparam(%p, %s, 0x%x, %d, %d, %d, %p)\n",
164  dbproc, paramname, status, db_type, maxlen, datalen, value);
165  CHECK_CONN(FAIL);
167 
169  type = (TDS_SERVER_TYPE) db_type;
170 
171  /* validate datalen parameter */
172 
173  if (is_fixed_type(type)) {
174  if (datalen != 0)
175  datalen = -1;
176  } else { /* Sybooks: "Passing datalen as -1 for any of these [non-fixed] datatypes results
177  * in the DBPROCESS referenced by dbproc being marked as "dead," or unusable."
178  */
179  DBPERROR_RETURN(datalen < 0, SYBERPIL);
180  }
181 
182  /* "value parameter for dbprcparam() can be NULL, only if the datalen parameter is 0." */
183  DBPERROR_RETURN(value == NULL && datalen != 0, SYBERPNULL);
184 
185  /* nullable types must provide a data length */
186  DBPERROR_RETURN(is_nullable_type(type) && datalen < 0, SYBERPUL);
187 
188  /* validate maxlen parameter */
189 
190  if (status & DBRPCRETURN) {
191  if (is_fixed_type(type)) {
192  maxlen = -1;
193  } else {
194  if (maxlen == -1)
195  maxlen = 255;
196  }
197  } else {
198  /*
199  * Well, maxlen should be used only for output parameter however it seems
200  * that ms implementation wrongly require this 0 for NULL variable
201  * input parameters, so fix it
202  */
203  DBPERROR_RETURN3(maxlen != -1 && maxlen != 0, SYBEIPV, (int) maxlen, "maxlen", "dbrpcparam");
204  maxlen = -1;
205  }
206 
207  /* end validation */
208 
209  /* This trick is to allow for client using utf8 to insert any character into a NVARCHAR parameter
210  * The 4000 check is to allow varchar with more then 4000 characters (varchar is limited to 8000
211  * characters) which can't be converted to nvarchar (which is limited to 4000 character)
212  */
214  && maxlen <= 4000 && datalen <= 4000)
215  type = XSYBNVARCHAR;
216 
217  /* allocate */
218  param = tds_new(DBREMOTE_PROC_PARAM, 1);
219  if (param == NULL) {
220  dbperror(dbproc, SYBEMEM, 0);
221  return FAIL;
222  }
223 
224  if (paramname) {
225  name = strdup(paramname);
226  if (name == NULL) {
227  free(param);
228  dbperror(dbproc, SYBEMEM, 0);
229  return FAIL;
230  }
231  }
232 
233  /* initialize */
234  param->next = NULL; /* NULL signifies end of linked list */
235  param->name = name;
236  param->status = status;
237  param->type = type;
238  param->maxlen = maxlen;
239  param->datalen = datalen;
240 
241  /*
242  * If datalen = 0, value parameter is ignored.
243  * This is one way to specify a NULL input parameter.
244  */
245  if (datalen == 0)
246  param->value = NULL;
247  else
248  param->value = value;
249 
250  /*
251  * Add a parameter to the current rpc.
252  *
253  * Traverse the dbproc's procedure list to find the current rpc,
254  * then traverse the parameter linked list until its end,
255  * then tack on our parameter's address.
256  */
257  for (rpc = dbproc->rpc; rpc->next != NULL; rpc = rpc->next) /* find "current" procedure */
258  continue;
259  for (pparam = &rpc->param_list; *pparam != NULL; pparam = &(*pparam)->next)
260  continue;
261 
262  /* pparam now contains the address of the end of the rpc's parameter list */
263 
264  *pparam = param; /* add to the end of the list */
265 
266  tdsdump_log(TDS_DBG_INFO1, "dbrpcparam() added parameter \"%s\"\n", (paramname) ? paramname : "");
267 
268  return SUCCEED;
269 }
270 
271 /**
272  * \ingroup dblib_rpc
273  * \brief Execute the procedure and free associated memory
274  *
275  * \param dbproc contains all information needed by db-lib to manage communications with the server.
276  * \retval SUCCEED normal.
277  * \retval FAIL on error
278  * \sa dbrpcinit(), dbrpcparam()
279  */
280 RETCODE
282 {
283  DBREMOTE_PROC *rpc;
284 
285  tdsdump_log(TDS_DBG_FUNC, "dbrpcsend(%p)\n", dbproc);
286  CHECK_CONN(FAIL);
287  CHECK_PARAMETER(dbproc->rpc, SYBERPCS, FAIL); /* dbrpcinit should allocate pointer */
288 
289  /* sanity */
290  if (dbproc->rpc->name == NULL) { /* can't be ready without a name */
291  tdsdump_log(TDS_DBG_INFO1, "returning FAIL: name is NULL\n");
292  return FAIL;
293  }
294 
296 
297  for (rpc = dbproc->rpc; rpc != NULL; rpc = rpc->next) {
298  TDSRET erc;
299  TDSPARAMINFO *pparam_info = NULL;
300 
301  /*
302  * liam@inodes.org: allow stored procedures to have no paramaters
303  */
304  if (rpc->param_list != NULL) {
305  pparam_info = param_info_alloc(dbproc->tds_socket, rpc);
306  if (!pparam_info)
307  return FAIL;
308  }
309  erc = tds_submit_rpc(dbproc->tds_socket, dbproc->rpc->name, pparam_info, NULL);
310  tds_free_param_results(pparam_info);
311  if (TDS_FAILED(erc)) {
312  tdsdump_log(TDS_DBG_INFO1, "returning FAIL: tds_submit_rpc() failed\n");
313  return FAIL;
314  }
315  }
316 
317  /* free up the memory */
318  rpc_clear(dbproc->rpc);
319  dbproc->rpc = NULL;
320 
321  tdsdump_log(TDS_DBG_FUNC, "dbrpcsend() returning SUCCEED\n");
322 
323  return SUCCEED;
324 }
325 
326 /**
327  * Tell the TDSPARAMINFO structure where the data go. This is a kind of "bind" operation.
328  */
329 static const unsigned char *
330 param_row_alloc(TDSPARAMINFO * params, TDSCOLUMN * curcol, int param_num, void *value, int size)
331 {
332  const void *row = tds_alloc_param_data(curcol);
333  tdsdump_log(TDS_DBG_INFO1, "parameter size = %d, data = %p, row_size = %d\n",
334  size, curcol->column_data, params->row_size);
335  if (!row)
336  return NULL;
337  if (size > 0 && value) {
338  tdsdump_log(TDS_DBG_FUNC, "copying %d bytes of data to parameter #%d\n", size, param_num);
339  if (!is_blob_col(curcol)) {
340  if (is_numeric_type(curcol->column_type))
341  memset(curcol->column_data, 0, sizeof(TDS_NUMERIC));
342  memcpy(curcol->column_data, value, size);
343  } else {
344  TDSBLOB *blob = (TDSBLOB *) curcol->column_data;
345  blob->textvalue = tds_new(TDS_CHAR, size);
346  tdsdump_log(TDS_DBG_FUNC, "blob parameter supported, size %d textvalue pointer is %p\n",
347  size, blob->textvalue);
348  if (!blob->textvalue)
349  return NULL;
350  memcpy(blob->textvalue, value, size);
351  }
352  }
353  else {
354  tdsdump_log(TDS_DBG_FUNC, "setting parameter #%d to NULL\n", param_num);
355  curcol->column_cur_size = -1;
356  }
357 
358  return (const unsigned char*) row;
359 }
360 
361 /**
362  * Allocate memory and copy the rpc information into a TDSPARAMINFO structure.
363  */
364 static TDSPARAMINFO *
366 {
367  int i;
369  TDSCOLUMN *pcol;
370  TDSPARAMINFO *params = NULL, *new_params;
371  BYTE *temp_value;
372  int temp_datalen;
373  TDS_SERVER_TYPE temp_type;
374  int param_is_null;
375 
376  /* sanity */
377  if (rpc == NULL)
378  return NULL;
379 
380  /* see v 1.10 2002/11/23 for first broken attempt */
381 
382  for (i = 0, p = rpc->param_list; p != NULL; p = p->next, i++) {
383  const unsigned char *prow;
384 
385  if (!(new_params = tds_alloc_param_result(params))) {
386  tds_free_param_results(params);
387  tdsdump_log(TDS_DBG_ERROR, "out of rpc memory!");
388  return NULL;
389  }
390  params = new_params;
391 
392  /*
393  * Determine whether an input parameter is NULL or not.
394  */
395  param_is_null = 0;
396  temp_type = p->type;
397  temp_value = p->value;
398  temp_datalen = p->datalen;
399 
400  if (p->datalen == 0)
401  param_is_null = 1;
402 
403  tdsdump_log(TDS_DBG_INFO1, "parm_info_alloc(): parameter null-ness = %d\n", param_is_null);
404 
405  pcol = params->columns[i];
406 
407  if (temp_value && is_numeric_type(temp_type)) {
408  DBDECIMAL *dec = (DBDECIMAL*) temp_value;
409  pcol->column_prec = dec->precision;
410  pcol->column_scale = dec->scale;
411  if (dec->precision > 0 && dec->precision <= MAXPRECISION)
412  temp_datalen = tds_numeric_bytes_per_prec[dec->precision] + 2;
413  }
414  if (param_is_null || (p->status & DBRPCRETURN)) {
415  if (param_is_null) {
416  temp_datalen = 0;
417  temp_value = NULL;
418  } else if (is_fixed_type(temp_type)) {
419  temp_datalen = tds_get_size_by_type(temp_type);
420  }
421  temp_type = tds_get_null_type(temp_type);
422  } else if (is_fixed_type(temp_type)) {
423  temp_datalen = tds_get_size_by_type(temp_type);
424  }
425 
426  /* meta data */
427  if (p->name)
428  if (!tds_dstr_copy(&pcol->column_name, p->name)) {
429  tds_free_param_results(params);
430  tdsdump_log(TDS_DBG_ERROR, "out of rpc memory!");
431  return NULL;
432  }
433 
434  tds_set_param_type(tds->conn, pcol, temp_type);
435 
436  if (p->maxlen > 0)
437  pcol->column_size = p->maxlen;
438  else {
439  if (is_fixed_type(p->type)) {
441  } else {
442  pcol->column_size = p->datalen;
443  }
444  }
445  if (p->type == XSYBNVARCHAR)
446  pcol->column_size *= 2;
447  pcol->on_server.column_size = pcol->column_size;
448 
449  pcol->column_output = p->status;
450  pcol->column_cur_size = temp_datalen;
451 
452  prow = param_row_alloc(params, pcol, i, temp_value, temp_datalen);
453 
454  if (!prow) {
455  tds_free_param_results(params);
456  tdsdump_log(TDS_DBG_ERROR, "out of memory for rpc row!");
457  return NULL;
458  }
459 
460  }
461 
462  return params;
463 
464 }
465 
466 /**
467  * erase the procedure list
468  */
469 static void
471 {
473 
474  while (rpc) {
475  next = rpc->next;
476  param_clear(rpc->param_list);
477  free(rpc->name);
478  free(rpc);
479  rpc = next;
480  }
481 }
482 
483 /**
484  * erase the parameter list
485  */
486 static void
488 {
490 
491  while (pparam) {
492  next = pparam->next;
493  free(pparam->name);
494  /* free self */
495  free(pparam);
496  pparam = next;
497  }
498 }
static void param_clear(DBREMOTE_PROC_PARAM *pparam)
erase the parameter list
Definition: rpc.c:487
static TDSPARAMINFO * param_info_alloc(TDSSOCKET *tds, DBREMOTE_PROC *rpc)
Allocate memory and copy the rpc information into a TDSPARAMINFO structure.
Definition: rpc.c:365
static const unsigned char * param_row_alloc(TDSPARAMINFO *params, TDSCOLUMN *curcol, int param_num, void *value, int size)
Tell the TDSPARAMINFO structure where the data go.
Definition: rpc.c:330
static void rpc_clear(DBREMOTE_PROC *rpc)
erase the procedure list
Definition: rpc.c:470
static DBPROCESS * dbproc
Definition: done_handling.c:29
#define DBPERROR_RETURN(x, msg)
Definition: dblib.h:196
#define CHECK_NULP(x, func, param_num, ret)
Definition: dblib.h:194
@ _DB_RES_INIT
Definition: dblib.h:34
#define CHECK_PARAMETER(x, msg, ret)
Definition: dblib.h:193
#define CHECK_CONN(ret)
Definition: dblib.h:198
#define DBPERROR_RETURN3(x, msg, a, b, c)
Definition: dblib.h:197
static DLIST_TYPE *DLIST_NAME() next(DLIST_LIST_TYPE *list, DLIST_TYPE *item)
Definition: dlist.tmpl.h:56
TDS_SERVER_TYPE
Definition: proto.h:161
@ XSYBNVARCHAR
Definition: proto.h:195
#define tds_new(type, n)
Definition: tds.h:1392
#define is_numeric_type(x)
Definition: tds.h:454
#define TDS_FAILED(rc)
Definition: tds.h:206
#define is_fixed_type(x)
Definition: tds.h:438
#define tdsdump_log
Definition: tds.h:1561
#define TDS_DBG_INFO1
Definition: tds.h:900
static bool is_tds_type_valid(int type)
Definition: tds.h:463
#define is_blob_col(x)
Definition: tds.h:445
#define IS_TDS7_PLUS(x)
Definition: tds.h:1708
#define tds_new0(type, n)
Definition: tds.h:1393
char TDS_CHAR
Definition: tds.h:144
#define is_nullable_type(x)
Definition: tds.h:439
int TDSRET
Definition: tds.h:201
#define TDS_DBG_ERROR
Definition: tds.h:903
#define MAXPRECISION
Definition: tds.h:470
#define TDS_DBG_FUNC
Definition: tds.h:898
static int type
Definition: getdata.c:31
#define DBRPCRESET
Definition: sybdb.h:598
#define SYBEMEM
Definition: sybdb.h:938
#define SYBEUDTY
Definition: sybdb.h:988
#define SYBVARCHAR
Definition: sybdb.h:162
int RETCODE
Definition: sybdb.h:121
#define DBRPCRECOMPILE
Definition: sybdb.h:597
#define SYBEIPV
Definition: sybdb.h:1122
unsigned char BYTE
Definition: sybdb.h:334
#define SYBERPUL
Definition: sybdb.h:1042
Int2 DBSMALLINT
Definition: sybdb.h:254
#define SYBERPIL
Definition: sybdb.h:1041
#define SUCCEED
Definition: sybdb.h:585
#define SYBERPCS
Definition: sybdb.h:1114
#define FAIL
Definition: sybdb.h:586
#define DBRPCRETURN
Definition: sybdb.h:577
#define SYBERPNULL
Definition: sybdb.h:1136
Int4 DBINT
Definition: sybdb.h:255
static TDSSOCKET * tds
Definition: collations.c:37
#define dbperror
#define tds_set_param_type
#define tds_free_param_results
#define tds_get_size_by_type
#define tds_alloc_param_result
#define tds_submit_rpc
#define tds_numeric_bytes_per_prec
#define tds_get_null_type
#define tds_alloc_param_data
#define NULL
Definition: ncbistd.hpp:225
RETCODE dbrpcinit(DBPROCESS *dbproc, const char rpcname[], DBSMALLINT options)
Initialize a remote procedure call.
Definition: rpc.c:72
RETCODE dbrpcparam(DBPROCESS *dbproc, const char paramname[], BYTE status, int db_type, DBINT maxlen, DBINT datalen, BYTE *value)
Add a parameter to a remote procedure call.
Definition: rpc.c:155
RETCODE dbrpcsend(DBPROCESS *dbproc)
Execute the procedure and free associated memory.
Definition: rpc.c:281
DSTR * tds_dstr_copy(DSTR *s, const char *src) TDS_WUR
copy a string from another
Definition: tdsstring.c:123
int i
const struct ncbi::grid::netcache::search::fields::SIZE size
const GenericPointer< typename T::ValueType > T2 value
Definition: pointer.h:1227
int strcmp(const char *str1, const char *str2)
Definition: odbc_utils.hpp:160
#define strdup
Definition: ncbi_ansi_ext.h:70
#define row(bind, expected)
Definition: string_bind.c:73
unsigned char scale
Definition: sybdb.h:279
unsigned char precision
Definition: sybdb.h:278
TDS_SERVER_TYPE type
Definition: dblib.h:94
struct DBREMOTE_PROC_PARAM * next
Definition: dblib.h:90
BYTE * value
Definition: dblib.h:97
struct DBREMOTE_PROC * next
Definition: dblib.h:102
DBREMOTE_PROC_PARAM * param_list
Definition: dblib.h:106
char * name
Definition: dblib.h:104
Information about blobs (e.g.
Definition: tds.h:658
TDS_CHAR * textvalue
Definition: tds.h:659
Metadata about columns in regular and compute rows.
Definition: tds.h:761
TDS_INT column_size
maximun size of data.
Definition: tds.h:766
DSTR column_name
Definition: tds.h:787
TDS_TINYINT column_prec
precision for decimal/numeric
Definition: tds.h:775
TDS_SERVER_TYPE column_type
This type can be different from wire type because conversion (e.g.
Definition: tds.h:768
TDS_TINYINT column_scale
scale for decimal/numeric
Definition: tds.h:776
unsigned char * column_data
Definition: tds.h:793
unsigned int column_output
Definition: tds.h:800
struct tds_column::@124 on_server
TDS_INT column_cur_size
size written in variable (ie: char, text, binary).
Definition: tds.h:811
TDSSOCKET * tds_socket
Definition: dblib.h:122
DBREMOTE_PROC * rpc
Definition: dblib.h:143
DB_RESULT_STATE dbresults_state
Definition: dblib.h:129
Hold information for any results.
Definition: tds.h:842
TDSCOLUMN ** columns
Definition: tds.h:844
TDS_INT row_size
Definition: tds.h:851
Information for a server connection.
Definition: tds.h:1211
TDSCONNECTION conn[1]
Definition: tds.h:1215
Definition: type.c:6
void free(voidpf ptr)
Modified on Sun Jun 16 04:33:47 2024 by modify_doxy.py rev. 669887