src/libcollectdclient: Add a library which abstracts talking to the `unixsock' plugin.
[collectd.git] / src / libcollectdclient / client.c
1 /**
2  * libcollectdclient - src/libcollectdclient/client.c
3  * Copyright (C) 2008  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 /* Set to C99 and POSIX code */
23 #ifndef _ISOC99_SOURCE
24 # define _ISOC99_SOURCE
25 #endif
26 #ifndef _POSIX_SOURCE
27 # define _POSIX_SOURCE
28 #endif
29 #ifndef _POSIX_C_SOURCE
30 # define _POSIX_C_SOURCE 200112L
31 #endif
32 #ifndef _REENTRANT
33 # define _REENTRANT
34 #endif
35
36 /* Disable non-standard extensions */
37 #ifdef _BSD_SOURCE
38 # undef _BSD_SOURCE
39 #endif
40 #ifdef _SVID_SOURCE
41 # undef _SVID_SOURCE
42 #endif
43 #ifdef _GNU_SOURCE
44 # undef _GNU_SOURCE
45 #endif
46
47 #if !defined(__GNUC__) || !__GNUC__
48 # define __attribute__(x) /**/
49 #endif
50
51 #include <stdlib.h>
52 #include <stdio.h>
53 #include <unistd.h>
54 #include <sys/types.h>
55 #include <sys/socket.h>
56 #include <sys/un.h>
57 #include <string.h>
58 #include <assert.h>
59 #include <errno.h>
60 #include <netdb.h>
61
62 #include "client.h"
63
64 #define SSTRCPY(d,s) do { \
65   strncpy ((d), (s), sizeof (d)); \
66   (d)[sizeof (d) - 1] = 0; \
67 } while (0)
68
69 #define LCC_SET_ERRSTR(c, ...) do { \
70   snprintf ((c)->errbuf, sizeof ((c)->errbuf), __VA_ARGS__); \
71   (c)->errbuf[sizeof ((c)->errbuf) - 1] = 0; \
72 } while (0)
73
74 #if 1
75 # define LCC_DEBUG(...) printf (__VA_ARGS__)
76 #else
77 # define LCC_DEBUG(...) /**/
78 #endif
79
80 /*
81  * Types
82  */
83 struct lcc_connection_s
84 {
85   FILE *fh;
86   char errbuf[1024];
87 };
88
89 struct lcc_response_s
90 {
91   int status;
92   char message[1024];
93   char **lines;
94   size_t lines_num;
95 };
96 typedef struct lcc_response_s lcc_response_t;
97
98 /*
99  * Private functions
100  */
101 static int lcc_set_errno (lcc_connection_t *c, int err) /* {{{ */
102 {
103   if (c == NULL)
104     return (-1);
105
106   strerror_r (err, c->errbuf, sizeof (c->errbuf));
107   c->errbuf[sizeof (c->errbuf) - 1] = 0;
108
109   return (0);
110 } /* }}} int lcc_set_errno */
111
112 /* lcc_strdup: Since `strdup' is an XSI extension, we provide our own version
113  * here. */
114 __attribute__((malloc, nonnull (1)))
115 static char *lcc_strdup (const char *str) /* {{{ */
116 {
117   size_t strsize;
118   char *ret;
119
120   strsize = strlen (str) + 1;
121   ret = (char *) malloc (strsize);
122   if (ret != NULL)
123     memcpy (ret, str, strsize);
124   return (ret);
125 } /* }}} char *lcc_strdup */
126
127 __attribute__((nonnull (1, 2)))
128 static char *lcc_strescape (char *dest, char *src, size_t dest_size) /* {{{ */
129 {
130   size_t dest_pos;
131   size_t src_pos;
132
133   dest_pos = 0;
134   src_pos = 0;
135
136   assert (dest_size >= 3);
137
138   dest[dest_pos] = '"';
139   dest_pos++;
140
141   while (42)
142   {
143     if ((dest_pos == (dest_size - 2))
144         || (src[src_pos] == 0))
145       break;
146
147     if ((src[src_pos] == '"') || (src[src_pos] == '\\'))
148     {
149       /* Check if there is enough space for both characters.. */
150       if (dest_pos == (dest_size - 3))
151         break;
152
153       dest[dest_pos] = '\\';
154       dest_pos++;
155     }
156
157     dest[dest_pos] = src[src_pos];
158     dest_pos++;
159     src_pos++;
160   }
161
162   assert (dest_pos <= (dest_size - 2));
163
164   dest[dest_pos] = '"';
165   dest_pos++;
166
167   dest[dest_pos] = 0;
168   dest_pos++;
169   src_pos++;
170
171   return (dest);
172 } /* }}} char *lcc_strescape */
173
174 /* lcc_chomp: Removes all control-characters at the end of a string. */
175 static void lcc_chomp (char *str) /* {{{ */
176 {
177   size_t str_len;
178
179   str_len = strlen (str);
180   while (str_len > 0)
181   {
182     if (str[str_len - 1] >= 32)
183       break;
184     str[str_len - 1] = 0;
185     str_len--;
186   }
187 } /* }}} void lcc_chomp */
188
189 static void lcc_response_free (lcc_response_t *res) /* {{{ */
190 {
191   size_t i;
192
193   if (res == NULL)
194     return;
195
196   for (i = 0; i < res->lines_num; i++)
197     free (res->lines[i]);
198   free (res->lines);
199   res->lines = NULL;
200 } /* }}} void lcc_response_free */
201
202 static int lcc_send (lcc_connection_t *c, const char *command) /* {{{ */
203 {
204   int status;
205
206   LCC_DEBUG ("send:    --> %s\n", command);
207
208   status = fprintf (c->fh, "%s\r\n", command);
209   if (status < 0)
210   {
211     lcc_set_errno (c, errno);
212     return (-1);
213   }
214
215   return (0);
216 } /* }}} int lcc_send */
217
218 static int lcc_receive (lcc_connection_t *c, /* {{{ */
219     lcc_response_t *ret_res)
220 {
221   lcc_response_t res;
222   char *ptr;
223   char buffer[4096];
224   size_t i;
225
226   memset (&res, 0, sizeof (res));
227
228   /* Read the first line, containing the status and a message */
229   ptr = fgets (buffer, sizeof (buffer), c->fh);
230   if (ptr == NULL)
231   {
232     lcc_set_errno (c, errno);
233     return (-1);
234   }
235   lcc_chomp (buffer);
236   LCC_DEBUG ("receive: <-- %s\n", buffer);
237
238   /* Convert the leading status to an integer and make `ptr' to point to the
239    * beginning of the message. */
240   ptr = NULL;
241   errno = 0;
242   res.status = strtol (buffer, &ptr, 0);
243   if ((errno != 0) || (ptr == &buffer[0]))
244   {
245     lcc_set_errno (c, errno);
246     return (-1);
247   }
248
249   /* Skip white spaces after the status number */
250   while ((*ptr == ' ') || (*ptr == '\t'))
251     ptr++;
252
253   /* Now copy the message. */
254   strncpy (res.message, ptr, sizeof (res.message));
255   res.message[sizeof (res.message) - 1] = 0;
256
257   /* Error or no lines follow: We're done. */
258   if (res.status <= 0)
259   {
260     memcpy (ret_res, &res, sizeof (res));
261     return (0);
262   }
263
264   /* Allocate space for the char-pointers */
265   res.lines_num = (size_t) res.status;
266   res.status = 0;
267   res.lines = (char **) malloc (res.lines_num * sizeof (char *));
268   if (res.lines == NULL)
269   {
270     lcc_set_errno (c, ENOMEM);
271     return (-1);
272   }
273
274   /* Now receive all the lines */
275   for (i = 0; i < res.lines_num; i++)
276   {
277     ptr = fgets (buffer, sizeof (buffer), c->fh);
278     if (ptr == NULL)
279     {
280       lcc_set_errno (c, errno);
281       break;
282     }
283     lcc_chomp (buffer);
284     LCC_DEBUG ("receive: <-- %s\n", buffer);
285
286     res.lines[i] = lcc_strdup (buffer);
287     if (res.lines[i] == NULL)
288     {
289       lcc_set_errno (c, ENOMEM);
290       break;
291     }
292   }
293
294   /* Check if the for-loop exited with an error. */
295   if (i < res.lines_num)
296   {
297     while (i > 0)
298     {
299       i--;
300       free (res.lines[i]);
301     }
302     free (res.lines);
303     return (-1);
304   }
305
306   memcpy (ret_res, &res, sizeof (res));
307   return (0);
308 } /* }}} int lcc_receive */
309
310 static int lcc_sendreceive (lcc_connection_t *c, /* {{{ */
311     const char *command, lcc_response_t *ret_res)
312 {
313   lcc_response_t res;
314   int status;
315
316   status = lcc_send (c, command);
317   if (status != 0)
318     return (status);
319
320   memset (&res, 0, sizeof (res));
321   status = lcc_receive (c, &res);
322   if (status == 0)
323     memcpy (ret_res, &res, sizeof (*ret_res));
324
325   return (status);
326 } /* }}} int lcc_sendreceive */
327
328 static int lcc_open_unixsocket (lcc_connection_t *c, const char *path) /* {{{ */
329 {
330   struct sockaddr_un sa;
331   int fd;
332   int status;
333
334   assert (c != NULL);
335   assert (c->fh == NULL);
336   assert (path != NULL);
337
338   fd = socket (PF_UNIX, SOCK_STREAM, /* protocol = */ 0);
339   if (fd < 0)
340   {
341     lcc_set_errno (c, errno);
342     return (-1);
343   }
344
345   memset (&sa, 0, sizeof (sa));
346   sa.sun_family = AF_UNIX;
347   strncpy (sa.sun_path, path, sizeof (sa.sun_path) - 1);
348
349   status = connect (fd, (struct sockaddr *) &sa, sizeof (sa));
350   if (status != 0)
351   {
352     lcc_set_errno (c, errno);
353     close (fd);
354     return (-1);
355   }
356
357   c->fh = fdopen (fd, "r+");
358   if (c->fh == NULL)
359   {
360     lcc_set_errno (c, errno);
361     close (fd);
362     return (-1);
363   }
364
365   return (0);
366 } /* }}} int lcc_open_unixsocket */
367
368 static int lcc_open_netsocket (lcc_connection_t *c, /* {{{ */
369     const char *addr_orig)
370 {
371   struct addrinfo ai_hints;
372   struct addrinfo *ai_res;
373   struct addrinfo *ai_ptr;
374   char addr_copy[NI_MAXHOST];
375   char *addr;
376   char *port;
377   int fd;
378   int status;
379
380   assert (c != NULL);
381   assert (c->fh == NULL);
382   assert (addr_orig != NULL);
383
384   strncpy(addr_copy, addr_orig, sizeof(addr_copy));
385   addr_copy[sizeof(addr_copy) - 1] = '\0';
386   addr = addr_copy;
387
388   memset (&ai_hints, 0, sizeof (ai_hints));
389   ai_hints.ai_flags = 0;
390 #ifdef AI_ADDRCONFIG
391   ai_hints.ai_flags |= AI_ADDRCONFIG;
392 #endif
393   ai_hints.ai_family = AF_UNSPEC;
394   ai_hints.ai_socktype = SOCK_STREAM;
395
396   port = NULL;
397   if (*addr == '[') /* IPv6+port format */
398   {
399     /* `addr' is something like "[2001:780:104:2:211:24ff:feab:26f8]:12345" */
400     addr++;
401
402     port = strchr (addr, ']');
403     if (port == NULL)
404     {
405       LCC_SET_ERRSTR (c, "malformed address: %s", addr_orig);
406       return (-1);
407     }
408     *port = 0;
409     port++;
410
411     if (*port == ':')
412       port++;
413     else if (*port == 0)
414       port = NULL;
415     else
416     {
417       LCC_SET_ERRSTR (c, "garbage after address: %s", port);
418       return (-1);
419     }
420   } /* if (*addr = ']') */
421   else if (strchr (addr, '.') != NULL) /* Hostname or IPv4 */
422   {
423     port = strrchr (addr, ':');
424     if (port != NULL)
425     {
426       *port = 0;
427       port++;
428     }
429   }
430
431   ai_res = NULL;
432   status = getaddrinfo (addr,
433                         port == NULL ? LCC_DEFAULT_PORT : port,
434                         &ai_hints, &ai_res);
435   if (status != 0)
436   {
437     LCC_SET_ERRSTR (c, "getaddrinfo: %s", gai_strerror (status));
438     return (-1);
439   }
440
441   for (ai_ptr = ai_res; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
442   {
443     fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
444     if (fd < 0)
445     {
446       status = errno;
447       fd = -1;
448       continue;
449     }
450
451     status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
452     if (status != 0)
453     {
454       status = errno;
455       close (fd);
456       fd = -1;
457       continue;
458     }
459
460     c->fh = fdopen (fd, "r+");
461     if (c->fh == NULL)
462     {
463       status = errno;
464       close (fd);
465       fd = -1;
466       continue;
467     }
468
469     assert (status == 0);
470     break;
471   } /* for (ai_ptr) */
472
473   if (status != 0)
474   {
475     lcc_set_errno (c, status);
476     return (-1);
477   }
478
479   return (0);
480 } /* }}} int lcc_open_netsocket */
481
482 static int lcc_open_socket (lcc_connection_t *c, const char *addr) /* {{{ */
483 {
484   int status = 0;
485
486   if (addr == NULL)
487     return (-1);
488
489   assert (c != NULL);
490   assert (c->fh == NULL);
491   assert (addr != NULL);
492
493   if (strncmp ("unix:", addr, strlen ("unix:")) == 0)
494     status = lcc_open_unixsocket (c, addr + strlen ("unix:"));
495   else if (addr[0] == '/')
496     status = lcc_open_unixsocket (c, addr);
497   else
498     status = lcc_open_netsocket (c, addr);
499
500   return (status);
501 } /* }}} int lcc_open_socket */
502
503 /*
504  * Public functions
505  */
506 int lcc_connect (const char *address, lcc_connection_t **ret_con) /* {{{ */
507 {
508   lcc_connection_t *c;
509
510   if (address == NULL)
511     return (-1);
512
513   if (ret_con == NULL)
514     return (-1);
515
516   c = (lcc_connection_t *) malloc (sizeof (*c));
517   if (c == NULL)
518     return (-1);
519   memset (c, 0, sizeof (*c));
520
521   *ret_con = c;
522   return (lcc_open_socket (c, address));
523 } /* }}} int lcc_connect */
524
525 int lcc_disconnect (lcc_connection_t *c) /* {{{ */
526 {
527   if (c == NULL)
528     return (-1);
529
530   if (c->fh != NULL)
531   {
532     fclose (c->fh);
533     c->fh = NULL;
534   }
535
536   free (c);
537   return (0);
538 } /* }}} int lcc_disconnect */
539
540 int lcc_getval (lcc_connection_t *c, lcc_identifier_t *ident, /* {{{ */
541     size_t *ret_values_num, gauge_t **ret_values, char ***ret_values_names)
542 {
543   char ident_str[6 * LCC_NAME_LEN];
544   char ident_esc[12 * LCC_NAME_LEN];
545   char command[14 * LCC_NAME_LEN];
546
547   lcc_response_t res;
548   size_t   values_num;
549   gauge_t *values = NULL;
550   char   **values_names = NULL;
551
552   size_t i;
553   int status;
554
555   if (c == NULL)
556     return (-1);
557
558   if (ident == NULL)
559   {
560     lcc_set_errno (c, EINVAL);
561     return (-1);
562   }
563
564   /* Build a commend with an escaped version of the identifier string. */
565   status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str), ident);
566   if (status != 0)
567     return (status);
568
569   snprintf (command, sizeof (command), "GETVAL %s",
570       lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
571   command[sizeof (command) - 1] = 0;
572
573   /* Send talk to the daemon.. */
574   status = lcc_sendreceive (c, command, &res);
575   if (status != 0)
576     return (status);
577
578   if (res.status != 0)
579   {
580     LCC_SET_ERRSTR (c, "Server error: %s", res.message);
581     return (-1);
582   }
583
584   values_num = res.lines_num;
585
586 #define BAIL_OUT(e) do { \
587   lcc_set_errno (c, (e)); \
588   free (values); \
589   if (values_names != NULL) { \
590     for (i = 0; i < values_num; i++) { \
591       free (values_names[i]); \
592     } \
593   } \
594   free (values_names); \
595   lcc_response_free (&res); \
596   return (-1); \
597 } while (0)
598
599   /* If neither the values nor the names are requested, return here.. */
600   if ((ret_values == NULL) && (ret_values_names == NULL))
601   {
602     if (ret_values_num != NULL)
603       *ret_values_num = values_num;
604     lcc_response_free (&res);
605     return (0);
606   }
607
608   /* Allocate space for the values */
609   if (ret_values != NULL)
610   {
611     values = (gauge_t *) malloc (values_num * sizeof (*values));
612     if (values == NULL)
613       BAIL_OUT (ENOMEM);
614   }
615
616   if (ret_values_names != NULL)
617   {
618     values_names = (char **) calloc (values_num, sizeof (*values_names));
619     if (values_names == NULL)
620       BAIL_OUT (ENOMEM);
621   }
622
623   for (i = 0; i < res.lines_num; i++)
624   {
625     char *key;
626     char *value;
627     char *endptr;
628
629     key = res.lines[i];
630     value = strchr (key, '=');
631     if (value == NULL)
632       BAIL_OUT (EPROTO);
633
634     *value = 0;
635     value++;
636
637     if (values != NULL)
638     {
639       endptr = NULL;
640       errno = 0;
641       values[i] = strtod (value, &endptr);
642
643       if ((endptr == value) || (errno != 0))
644         BAIL_OUT (errno);
645     }
646
647     if (values_names != NULL)
648     {
649       values_names[i] = lcc_strdup (key);
650       if (values_names[i] == NULL)
651         BAIL_OUT (ENOMEM);
652     }
653   } /* for (i = 0; i < res.lines_num; i++) */
654
655   if (ret_values_num != NULL)
656     *ret_values_num = values_num;
657   if (ret_values != NULL)
658     *ret_values = values;
659   if (ret_values_names != NULL)
660     *ret_values_names = values_names;
661
662   return (0);
663 } /* }}} int lcc_getval */
664
665 /* TODO: Implement lcc_putval */
666 int lcc_putval (lcc_connection_t *c, const lcc_value_list_t *vl);
667
668 /* TODO: Implement lcc_flush */
669 int lcc_flush (lcc_connection_t *c, lcc_identifier_t *ident, int timeout);
670
671 /* TODO: Implement lcc_putnotif */
672
673 int lcc_listval (lcc_connection_t *c, /* {{{ */
674     lcc_identifier_t **ret_ident, size_t *ret_ident_num)
675 {
676   lcc_response_t res;
677   size_t i;
678   int status;
679
680   lcc_identifier_t *ident;
681   size_t ident_num;
682
683   if (c == NULL)
684     return (-1);
685
686   if ((ret_ident == NULL) || (ret_ident_num == NULL))
687   {
688     lcc_set_errno (c, EINVAL);
689     return (-1);
690   }
691
692   status = lcc_sendreceive (c, "LISTVAL", &res);
693   if (status != 0)
694     return (status);
695
696   if (res.status != 0)
697   {
698     LCC_SET_ERRSTR (c, "Server error: %s", res.message);
699     return (-1);
700   }
701
702   ident_num = res.lines_num;
703   ident = (lcc_identifier_t *) malloc (ident_num * sizeof (*ident));
704   if (ident == NULL)
705   {
706     lcc_response_free (&res);
707     lcc_set_errno (c, ENOMEM);
708     return (-1);
709   }
710
711   for (i = 0; i < res.lines_num; i++)
712   {
713     char *time_str;
714     char *ident_str;
715
716     /* First field is the time. */
717     time_str = res.lines[i];
718
719     /* Set `ident_str' to the beginning of the second field. */
720     ident_str = time_str;
721     while ((*ident_str != ' ') && (*ident_str != '\t') && (*ident_str != 0))
722       ident_str++;
723     while ((*ident_str == ' ') || (*ident_str == '\t'))
724     {
725       *ident_str = 0;
726       ident_str++;
727     }
728
729     if (*ident_str == 0)
730     {
731       lcc_set_errno (c, EPROTO);
732       status = -1;
733       break;
734     }
735
736     status = lcc_string_to_identifier (c, ident + i, ident_str);
737     if (status != 0)
738       break;
739   }
740
741   lcc_response_free (&res);
742
743   if (status != 0)
744   {
745     free (ident);
746     return (-1);
747   }
748
749   *ret_ident = ident;
750   *ret_ident_num = ident_num;
751
752   return (0);
753 } /* }}} int lcc_listval */
754
755 const char *lcc_strerror (lcc_connection_t *c) /* {{{ */
756 {
757   if (c == NULL)
758     return ("Invalid object");
759   return (c->errbuf);
760 } /* }}} const char *lcc_strerror */
761
762 int lcc_identifier_to_string (lcc_connection_t *c, /* {{{ */
763     char *string, size_t string_size, const lcc_identifier_t *ident)
764 {
765   if ((string == NULL) || (string_size < 6) || (ident == NULL))
766   {
767     lcc_set_errno (c, EINVAL);
768     return (-1);
769   }
770
771   if (ident->plugin_instance[0] == 0)
772   {
773     if (ident->type_instance[0] == 0)
774       snprintf (string, string_size, "%s/%s/%s",
775           ident->host,
776           ident->plugin,
777           ident->type);
778     else
779       snprintf (string, string_size, "%s/%s/%s-%s",
780           ident->host,
781           ident->plugin,
782           ident->type,
783           ident->type_instance);
784   }
785   else
786   {
787     if (ident->type_instance[0] == 0)
788       snprintf (string, string_size, "%s/%s-%s/%s",
789           ident->host,
790           ident->plugin,
791           ident->plugin_instance,
792           ident->type);
793     else
794       snprintf (string, string_size, "%s/%s-%s/%s-%s",
795           ident->host,
796           ident->plugin,
797           ident->plugin_instance,
798           ident->type,
799           ident->type_instance);
800   }
801
802   string[string_size - 1] = 0;
803   return (0);
804 } /* }}} int lcc_identifier_to_string */
805
806 int lcc_string_to_identifier (lcc_connection_t *c, /* {{{ */
807     lcc_identifier_t *ident, const char *string)
808 {
809   char *string_copy;
810   char *host;
811   char *plugin;
812   char *plugin_instance;
813   char *type;
814   char *type_instance;
815
816   string_copy = lcc_strdup (string);
817   if (string_copy == NULL)
818   {
819     lcc_set_errno (c, ENOMEM);
820     return (-1);
821   }
822
823   host = string_copy;
824   plugin = strchr (host, '/');
825   if (plugin == NULL)
826   {
827     LCC_SET_ERRSTR (c, "Malformed identifier string: %s", string);
828     free (string_copy);
829     return (-1);
830   }
831   *plugin = 0;
832   plugin++;
833
834   type = strchr (plugin, '/');
835   if (type == NULL)
836   {
837     LCC_SET_ERRSTR (c, "Malformed identifier string: %s", string);
838     free (string_copy);
839     return (-1);
840   }
841   *type = 0;
842   type++;
843
844   plugin_instance = strchr (plugin, '-');
845   if (plugin_instance != NULL)
846   {
847     *plugin_instance = 0;
848     plugin_instance++;
849   }
850
851   type_instance = strchr (type, '-');
852   if (type_instance != NULL)
853   {
854     *type_instance = 0;
855     type_instance++;
856   }
857
858   memset (ident, 0, sizeof (*ident));
859
860   SSTRCPY (ident->host, host);
861   SSTRCPY (ident->plugin, plugin);
862   if (plugin_instance != NULL)
863     SSTRCPY (ident->plugin_instance, plugin_instance);
864   SSTRCPY (ident->type, type);
865   if (type_instance != NULL)
866     SSTRCPY (ident->type_instance, type_instance);
867
868   free (string_copy);
869   return (0);
870 } /* }}} int lcc_string_to_identifier */
871
872 /* vim: set sw=2 sts=2 et fdm=marker : */