Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : /*
14 : * Clients gain access to the Monet server through a internet connection.
15 : * Access through the internet requires a client program at the source,
16 : * which addresses the default port of a running server. It is a textual
17 : * interface for expert use.
18 : *
19 : * At the server side, each client is represented by a session record
20 : * with the current status, such as name, file descriptors, namespace,
21 : * and local stack. Each client session has a dedicated thread of
22 : * control.
23 : *
24 : * The number of clients permitted concurrent access is a run time
25 : * option.
26 : *
27 : * Client sessions remain in existence until the corresponding
28 : * communication channels break.
29 : *
30 : * A client record is initialized upon acceptance of a connection. The
31 : * client runs in his own thread of control until it finds a
32 : * soft-termination request mode (FINISHCLIENT) or its IO file descriptors
33 : * are closed. The latter generates an IO error, which leads to a safe
34 : * termination.
35 : *
36 : * The system administrator client runs in the primary thread of control
37 : * to simplify debugging with external debuggers.
38 : *
39 : * Searching a free client record is encapsulated in a critical section
40 : * to hand them out one-at-a-time. Marking them as being claimed avoids
41 : * any interference from parallel actions to obtain client records.
42 : */
43 :
44 : /* (author) M.L. Kersten */
45 : #include "monetdb_config.h"
46 : #include "mal_client.h"
47 : #include "mal_import.h"
48 : #include "mal_parser.h"
49 : #include "mal_namespace.h"
50 : #include "mal_private.h"
51 : #include "mal_internal.h"
52 : #include "mal_interpreter.h"
53 : #include "mal_runtime.h"
54 : #include "mal_authorize.h"
55 : #include "mapi_prompt.h"
56 :
57 : int MAL_MAXCLIENTS = 0;
58 : ClientRec *mal_clients = NULL;
59 :
60 : void
61 326 : mal_client_reset(void)
62 : {
63 326 : if (mal_clients) {
64 326 : GDKfree(mal_clients);
65 326 : mal_clients = NULL;
66 : }
67 326 : MAL_MAXCLIENTS = 0;
68 326 : }
69 :
70 : bool
71 328 : MCinit(void)
72 : {
73 328 : const char *max_clients = GDKgetenv("max_clients");
74 328 : int maxclients = 0;
75 :
76 328 : if (max_clients != NULL)
77 2 : maxclients = atoi(max_clients);
78 2 : if (maxclients <= 0) {
79 326 : maxclients = 64;
80 326 : if (GDKsetenv("max_clients", "64") != GDK_SUCCEED) {
81 0 : TRC_CRITICAL(MAL_SERVER,
82 : "Initialization failed: " MAL_MALLOC_FAIL "\n");
83 0 : return false;
84 : }
85 : }
86 :
87 328 : MAL_MAXCLIENTS = /* client connections */ maxclients;
88 328 : mal_clients = GDKzalloc(sizeof(ClientRec) * MAL_MAXCLIENTS);
89 328 : if (mal_clients == NULL) {
90 0 : TRC_CRITICAL(MAL_SERVER,
91 : "Initialization failed: " MAL_MALLOC_FAIL "\n");
92 0 : return false;
93 : }
94 23244 : for (int i = 0; i < MAL_MAXCLIENTS; i++) {
95 22916 : ATOMIC_INIT(&mal_clients[i].lastprint, 0);
96 22916 : ATOMIC_INIT(&mal_clients[i].workers, 1);
97 22916 : ATOMIC_INIT(&mal_clients[i].qryctx.datasize, 0);
98 22916 : mal_clients[i].idx = -1; /* indicate it's available */
99 : }
100 : return true;
101 : }
102 :
103 : /* stack the files from which you read */
104 : int
105 0 : MCpushClientInput(Client c, bstream *new_input, int listing, const char *prompt)
106 : {
107 0 : ClientInput *x = (ClientInput *) GDKmalloc(sizeof(ClientInput));
108 0 : if (x == 0)
109 : return -1;
110 0 : *x = (ClientInput) {
111 0 : .fdin = c->fdin,
112 0 : .yycur = c->yycur,
113 0 : .listing = c->listing,
114 0 : .prompt = c->prompt,
115 0 : .next = c->bak,
116 : };
117 0 : c->bak = x;
118 0 : c->fdin = new_input;
119 0 : c->qryctx.bs = new_input;
120 0 : c->listing = listing;
121 0 : c->prompt = prompt ? prompt : "";
122 0 : c->promptlength = strlen(c->prompt);
123 0 : c->yycur = 0;
124 0 : return 0;
125 : }
126 :
127 : void
128 0 : MCpopClientInput(Client c)
129 : {
130 0 : ClientInput *x = c->bak;
131 0 : if (c->fdin) {
132 : /* missing protection against closing stdin stream */
133 0 : bstream_destroy(c->fdin);
134 : }
135 0 : c->fdin = x->fdin;
136 0 : c->qryctx.bs = c->fdin;
137 0 : c->yycur = x->yycur;
138 0 : c->listing = x->listing;
139 0 : c->prompt = x->prompt;
140 0 : c->promptlength = strlen(c->prompt);
141 0 : c->bak = x->next;
142 0 : GDKfree(x);
143 0 : }
144 :
145 : static Client
146 37716 : MCnewClient(void)
147 : {
148 494724 : for (Client c = mal_clients; c < mal_clients + MAL_MAXCLIENTS; c++) {
149 494724 : if (c->idx == -1) {
150 37716 : assert(c->mode == FREECLIENT);
151 37716 : c->mode = RUNCLIENT;
152 37716 : c->idx = (int) (c - mal_clients);
153 37716 : return c;
154 : }
155 : }
156 :
157 : return NULL;
158 : }
159 :
160 : /*
161 : * You can always retrieve a client record using the thread identifier,
162 : * because we maintain a 1-1 mapping between client and thread of
163 : * control. Therefore, we don't need locks either.
164 : * If the number of clients becomes too large, we have to change the
165 : * allocation and lookup scheme.
166 : *
167 : * Finding a client record is tricky when we are spawning threads as
168 : * co-workers. It is currently passed as an argument.
169 : */
170 :
171 : Client
172 368572 : MCgetClient(int id)
173 : {
174 368572 : if (id <0 || id >=MAL_MAXCLIENTS)
175 : return NULL;
176 368572 : return mal_clients + id;
177 : }
178 :
179 : /*
180 : * The resetProfiler is called when the owner of the event stream
181 : * leaves the scene. (Unclear if parallelism may cause errors)
182 : */
183 :
184 : static void
185 37715 : MCresetProfiler(stream *fdout)
186 : {
187 37715 : MT_lock_set(&mal_profileLock);
188 37715 : if (fdout == maleventstream) {
189 0 : maleventstream = NULL;
190 0 : profilerStatus = 0;
191 0 : profilerMode = 0;
192 : }
193 37715 : MT_lock_unset(&mal_profileLock);
194 37715 : }
195 :
196 : static void
197 37715 : MCexitClient(Client c)
198 : {
199 37715 : MCresetProfiler(c->fdout);
200 : // Remove any left over constant symbols
201 37715 : if (c->curprg)
202 327 : resetMalBlk(c->curprg->def);
203 37714 : if (c->father == NULL) { /* normal client */
204 37714 : if (c->fdout && c->fdout != GDKstdout)
205 37364 : close_stream(c->fdout);
206 37715 : assert(c->bak == NULL);
207 37715 : if (c->fdin) {
208 : /* protection against closing stdin stream */
209 37715 : if (c->fdin->s == GDKstdin)
210 350 : c->fdin->s = NULL;
211 37715 : bstream_destroy(c->fdin);
212 : }
213 37715 : c->fdout = NULL;
214 37715 : c->fdin = NULL;
215 37715 : c->qryctx.bs = NULL;
216 : }
217 37715 : assert(c->query == NULL);
218 37715 : if (profilerStatus > 0) {
219 0 : lng Tend = GDKusec();
220 0 : profilerEvent(NULL,
221 : &(struct NonMalEvent)
222 0 : { CLIENT_END, c, Tend, NULL, NULL, 0,
223 0 : Tend - (c->session) });
224 : }
225 37715 : }
226 :
227 : static Client
228 37716 : MCinitClientRecord(Client c, oid user, bstream *fin, stream *fout)
229 : {
230 : /* mal_contextLock is held when this is called */
231 37716 : c->user = user;
232 37716 : c->username = 0;
233 37716 : c->scenario = NULL;
234 37716 : c->srcFile = NULL;
235 37716 : c->blkmode = 0;
236 :
237 37716 : c->fdin = fin ? fin : bstream_create(GDKstdin, 0);
238 37716 : if (c->fdin == NULL) {
239 0 : c->mode = FREECLIENT;
240 0 : c->idx = -1;
241 0 : TRC_ERROR(MAL_SERVER, "No stdin channel available\n");
242 0 : return NULL;
243 : }
244 37716 : c->qryctx.bs = c->fdin;
245 37716 : c->yycur = 0;
246 37716 : c->bak = NULL;
247 :
248 37716 : c->listing = 0;
249 37716 : c->fdout = fout ? fout : GDKstdout;
250 37716 : c->curprg = c->backup = 0;
251 37716 : c->glb = 0;
252 :
253 : /* remove garbage from previous connection
254 : * be aware, a user can introduce several modules
255 : * that should be freed to avoid memory leaks */
256 37716 : c->usermodule = c->curmodule = 0;
257 :
258 37716 : c->father = NULL;
259 37716 : c->idle = c->login = c->lastcmd = time(0);
260 37716 : c->session = GDKusec();
261 37716 : strcpy_len(c->optimizer, "default_pipe", sizeof(c->optimizer));
262 37716 : c->workerlimit = 0;
263 37716 : c->memorylimit = 0;
264 37716 : c->querytimeout = 0;
265 37716 : c->sessiontimeout = 0;
266 37716 : c->logical_sessiontimeout = 0;
267 37716 : c->qryctx.starttime = 0;
268 37716 : c->qryctx.endtime = 0;
269 37716 : ATOMIC_SET(&c->qryctx.datasize, 0);
270 37716 : c->qryctx.maxmem = 0;
271 37716 : c->maxmem = 0;
272 37716 : c->errbuf = 0;
273 :
274 37716 : c->prompt = PROMPT1;
275 37716 : c->promptlength = strlen(c->prompt);
276 :
277 37716 : c->profticks = c->profstmt = c->profevents = NULL;
278 37716 : c->error_row = c->error_fld = c->error_msg = c->error_input = NULL;
279 37716 : c->sqlprofiler = 0;
280 37716 : c->blocksize = BLOCK;
281 37716 : c->protocol = PROTOCOL_9;
282 :
283 37716 : c->filetrans = false;
284 37716 : c->handshake_options = NULL;
285 37716 : c->query = NULL;
286 :
287 37716 : char name[MT_NAME_LEN];
288 37716 : snprintf(name, sizeof(name), "Client%d->s", (int) (c - mal_clients));
289 37716 : MT_sema_init(&c->s, 0, name);
290 37716 : return c;
291 : }
292 :
293 : Client
294 37716 : MCinitClient(oid user, bstream *fin, stream *fout)
295 : {
296 37716 : Client c = NULL;
297 :
298 37716 : MT_lock_set(&mal_contextLock);
299 37716 : c = MCnewClient();
300 37716 : if (c) {
301 37716 : c = MCinitClientRecord(c, user, fin, fout);
302 37716 : MT_thread_set_qry_ctx(&c->qryctx);
303 : }
304 37716 : MT_lock_unset(&mal_contextLock);
305 :
306 37716 : if (c && profilerStatus > 0)
307 0 : profilerEvent(NULL,
308 : &(struct NonMalEvent)
309 0 : { CLIENT_START, c, c->session, NULL, NULL, 0, 0 }
310 : );
311 37716 : return c;
312 : }
313 :
314 :
315 : /*
316 : * The administrator should be initialized to enable interpretation of
317 : * the command line arguments, before it starts servicing statements
318 : */
319 : int
320 37660 : MCinitClientThread(Client c)
321 : {
322 : /*
323 : * The GDK thread administration should be set to reflect use of
324 : * the proper IO descriptors.
325 : */
326 37660 : c->mythread = MT_thread_getname();
327 37665 : c->errbuf = GDKerrbuf;
328 37668 : if (c->errbuf == NULL) {
329 37668 : char *n = GDKzalloc(GDKMAXERRLEN);
330 37679 : if (n == NULL) {
331 0 : MCresetProfiler(c->fdout);
332 0 : return -1;
333 : }
334 37679 : GDKsetbuf(n);
335 37677 : c->errbuf = GDKerrbuf;
336 : } else
337 0 : c->errbuf[0] = 0;
338 : return 0;
339 : }
340 :
341 : static bool shutdowninprogress = false;
342 :
343 : bool
344 0 : MCshutdowninprogress(void)
345 : {
346 0 : MT_lock_set(&mal_contextLock);
347 0 : bool ret = shutdowninprogress;
348 0 : MT_lock_unset(&mal_contextLock);
349 0 : return ret;
350 : }
351 :
352 : /*
353 : * When a client needs to be terminated then the file descriptors for
354 : * its input/output are simply closed. This leads to a graceful
355 : * degradation, but may take some time when the client is busy. A more
356 : * forceful method is to kill the client thread, but this may leave
357 : * locks and semaphores in an undesirable state.
358 : *
359 : * The routine freeClient ends a single client session, but through side
360 : * effects of sharing IO descriptors, also its children. Conversely, a
361 : * child can not close a parent.
362 : */
363 : void
364 37713 : MCcloseClient(Client c)
365 : {
366 37713 : MT_lock_set(&mal_contextLock);
367 37715 : if (c->mode == FREECLIENT) {
368 0 : assert(c->idx == -1);
369 0 : MT_lock_unset(&mal_contextLock);
370 0 : return;
371 : }
372 37715 : c->mode = FINISHCLIENT;
373 37715 : MT_lock_unset(&mal_contextLock);
374 :
375 37715 : MCexitClient(c);
376 :
377 : /* scope list and curprg can not be removed, because the client may
378 : * reside in a quit() command. Therefore the scopelist is re-used.
379 : */
380 37715 : c->scenario = NULL;
381 37715 : c->prompt = NULL;
382 37715 : c->promptlength = -1;
383 37715 : if (c->errbuf) {
384 : /* no client threads in embedded mode */
385 37679 : GDKsetbuf(NULL);
386 37679 : if (c->father == NULL)
387 37679 : GDKfree(c->errbuf);
388 37679 : c->errbuf = NULL;
389 : }
390 37715 : if (c->usermodule)
391 330 : freeModule(c->usermodule);
392 37715 : c->usermodule = c->curmodule = 0;
393 37715 : c->father = 0;
394 37715 : strcpy_len(c->optimizer, "default_pipe", sizeof(c->optimizer));
395 37714 : c->workerlimit = 0;
396 37714 : c->memorylimit = 0;
397 37714 : c->querytimeout = 0;
398 37714 : c->qryctx.endtime = 0;
399 37714 : c->sessiontimeout = 0;
400 37714 : c->logical_sessiontimeout = 0;
401 37714 : c->user = oid_nil;
402 37714 : if (c->username) {
403 37361 : GDKfree(c->username);
404 37362 : c->username = 0;
405 : }
406 37715 : if (c->peer) {
407 37362 : GDKfree(c->peer);
408 37362 : c->peer = 0;
409 : }
410 37715 : if (c->client_hostname) {
411 36849 : GDKfree(c->client_hostname);
412 36849 : c->client_hostname = 0;
413 : }
414 37715 : if (c->client_application) {
415 36849 : GDKfree(c->client_application);
416 36849 : c->client_application = 0;
417 : }
418 37715 : if (c->client_library) {
419 36849 : GDKfree(c->client_library);
420 36849 : c->client_library = 0;
421 : }
422 37715 : if (c->client_remark) {
423 11 : GDKfree(c->client_remark);
424 11 : c->client_remark = 0;
425 : }
426 37715 : c->client_pid = 0;
427 37715 : c->mythread = NULL;
428 37715 : if (c->glb) {
429 37711 : freeStack(c->glb);
430 37711 : c->glb = NULL;
431 : }
432 37715 : if (c->profticks) {
433 26 : BBPunfix(c->profticks->batCacheid);
434 26 : BBPunfix(c->profstmt->batCacheid);
435 26 : BBPunfix(c->profevents->batCacheid);
436 26 : c->profticks = c->profstmt = c->profevents = NULL;
437 : }
438 37715 : if (c->error_row) {
439 484 : BBPunfix(c->error_row->batCacheid);
440 484 : BBPunfix(c->error_fld->batCacheid);
441 484 : BBPunfix(c->error_msg->batCacheid);
442 484 : BBPunfix(c->error_input->batCacheid);
443 484 : c->error_row = c->error_fld = c->error_msg = c->error_input = NULL;
444 : }
445 37715 : c->sqlprofiler = 0;
446 37715 : free(c->handshake_options);
447 37715 : c->handshake_options = NULL;
448 37715 : MT_thread_set_qry_ctx(NULL);
449 37713 : assert(c->qryctx.datasize == 0);
450 37713 : MT_sema_destroy(&c->s);
451 37714 : MT_lock_set(&mal_contextLock);
452 37715 : c->idle = c->login = c->lastcmd = 0;
453 37715 : if (shutdowninprogress) {
454 32 : c->mode = BLOCKCLIENT;
455 : } else {
456 37683 : c->mode = FREECLIENT;
457 37683 : c->idx = -1;
458 : }
459 37715 : MT_lock_unset(&mal_contextLock);
460 : }
461 :
462 : /*
463 : * If a client disappears from the scene (eof on stream), we should
464 : * terminate all its children. This is in principle a forceful action,
465 : * because the children may be ignoring the primary IO streams.
466 : * (Instead they may be blocked in an infinite loop)
467 : *
468 : * Special care should be taken by closing the 'adm' thread. It is
469 : * permitted to leave only when it is the sole user of the system.
470 : *
471 : * Furthermore, once we enter closeClient, the process in which it is
472 : * raised has already lost its file descriptors.
473 : *
474 : * When the server is about to shutdown, we should softly terminate
475 : * all outstanding session.
476 : */
477 : void
478 328 : MCstopClients(Client cntxt)
479 : {
480 328 : MT_lock_set(&mal_contextLock);
481 23244 : for (int i = 0; i < MAL_MAXCLIENTS; i++) {
482 22916 : Client c = mal_clients + i;
483 22916 : if (cntxt != c) {
484 22914 : if (c->mode == RUNCLIENT)
485 0 : c->mode = FINISHCLIENT;
486 22914 : else if (c->mode == FREECLIENT) {
487 22759 : assert(c->idx == -1);
488 22759 : c->idx = i;
489 22759 : c->mode = BLOCKCLIENT;
490 : }
491 : }
492 : }
493 328 : shutdowninprogress = true;
494 328 : MT_lock_unset(&mal_contextLock);
495 328 : }
496 :
497 : int
498 47787 : MCactiveClients(void)
499 : {
500 47787 : int active = 0;
501 :
502 47787 : MT_lock_set(&mal_contextLock);
503 4193871 : for (Client cntxt = mal_clients; cntxt < mal_clients + MAL_MAXCLIENTS;
504 4146084 : cntxt++) {
505 8110886 : active += (cntxt->idle == 0 && cntxt->mode == RUNCLIENT);
506 : }
507 47787 : MT_lock_unset(&mal_contextLock);
508 47787 : return active;
509 : }
510 :
511 : str
512 0 : MCsuspendClient(int id)
513 : {
514 0 : if (id <0 || id >=MAL_MAXCLIENTS)
515 0 : throw(INVCRED, "mal.clients", INVCRED_WRONG_ID);
516 : return MAL_SUCCEED;
517 : }
518 :
519 : str
520 0 : MCawakeClient(int id)
521 : {
522 0 : if (id <0 || id >=MAL_MAXCLIENTS)
523 0 : throw(INVCRED, "mal.clients", INVCRED_WRONG_ID);
524 : return MAL_SUCCEED;
525 : }
526 :
527 : /*
528 : * Input to be processed is collected in a Client specific buffer. It
529 : * is filled by reading information from a stream, a terminal, or by
530 : * scheduling strings constructed internally. The latter involves
531 : * removing any escape character needed to manipulate the string within
532 : * the kernel. The buffer space is automatically expanded to
533 : * accommodate new information and the read pointers are adjusted.
534 : *
535 : * The input is read from a (blocked) stream and stored in the client
536 : * record input buffer. The storage area grows automatically upon need.
537 : * The origin of the input stream depends on the connectivity mode.
538 : *
539 : * Each operation received from a front-end consists of at least one
540 : * line. To simplify misaligned communication with front-ends, we use
541 : * different prompts structures.
542 : *
543 : * The default action is to read information from an ascii-stream one
544 : * line at a time. This is the preferred mode for reading from terminal.
545 : *
546 : * The next statement block is to be read. Send a prompt to warn the
547 : * front-end to issue the request.
548 : */
549 : int
550 10995 : MCreadClient(Client c)
551 : {
552 10995 : bstream *in = c->fdin;
553 :
554 12397 : while (in->pos < in->len &&
555 1402 : (isspace((unsigned char) (in->buf[in->pos])) ||
556 0 : in->buf[in->pos] == ';' || !in->buf[in->pos]))
557 1402 : in->pos++;
558 :
559 10995 : if (in->pos >= in->len || in->mode) {
560 10995 : ssize_t rd;
561 :
562 10995 : if (in->eof || !isa_block_stream(c->fdout)) {
563 10995 : if (!isa_block_stream(c->fdout) && c->promptlength > 0)
564 0 : mnstr_write(c->fdout, c->prompt, c->promptlength, 1);
565 10995 : mnstr_flush(c->fdout, MNSTR_FLUSH_DATA);
566 10987 : in->eof = false;
567 : }
568 21475 : while ((rd = bstream_next(in)) > 0 && !in->eof) {
569 10488 : if (!in->mode) /* read one line at a time in line mode */
570 : break;
571 : }
572 10992 : if (rd < 0) {
573 : /* force end of stream handling below */
574 0 : in->pos = in->len;
575 10992 : } else if (in->mode) { /* find last new line */
576 10984 : char *p = in->buf + in->len - 1;
577 :
578 10984 : while (p > in->buf && *p != '\n') {
579 0 : *(p + 1) = *p;
580 0 : p--;
581 : }
582 10984 : if (p > in->buf)
583 10494 : *(p + 1) = 0;
584 10984 : if (p != in->buf + in->len - 1)
585 0 : in->len++;
586 : }
587 : }
588 10992 : if (in->pos >= in->len) {
589 : /* end of stream reached */
590 505 : if (c->bak) {
591 6 : MCpopClientInput(c);
592 0 : if (c->fdin == NULL)
593 : return 0;
594 : return MCreadClient(c);
595 : }
596 : return 0;
597 : }
598 : return 1;
599 : }
600 :
601 : int
602 51 : MCvalid(Client tc)
603 : {
604 51 : if (tc == NULL) {
605 : return 0;
606 : }
607 51 : MT_lock_set(&mal_contextLock);
608 111 : for (Client c = mal_clients; c < mal_clients + MAL_MAXCLIENTS; c++) {
609 111 : if (c == tc && c->mode == RUNCLIENT) {
610 51 : MT_lock_unset(&mal_contextLock);
611 51 : return 1;
612 : }
613 : }
614 0 : MT_lock_unset(&mal_contextLock);
615 0 : return 0;
616 : }
617 :
618 : void
619 183008 : MCsetClientInfo(Client c, const char *property, const char *value)
620 : {
621 183008 : if (strlen(property) < 7)
622 : return;
623 :
624 : // 012345 6 78...
625 : // Client H ostname
626 : // Applic a tionName
627 : // Client L ibrary
628 : // Client R emark
629 : // Client P id
630 183008 : int discriminant = toupper(property[6]);
631 :
632 183008 : switch (discriminant) {
633 36835 : case 'H':
634 36835 : if (strcasecmp(property, "ClientHostname") == 0) {
635 36841 : GDKfree(c->client_hostname);
636 73672 : c->client_hostname = value ? GDKstrdup(value) : NULL;
637 : }
638 : break;
639 36839 : case 'A':
640 36839 : if (strcasecmp(property, "ApplicationName") == 0) {
641 36841 : GDKfree(c->client_application);
642 73688 : c->client_application = value ? GDKstrdup(value) : NULL;
643 : }
644 : break;
645 36846 : case 'L':
646 36846 : if (strcasecmp(property, "ClientLibrary") == 0) {
647 36846 : GDKfree(c->client_library);
648 73695 : c->client_library = value ? GDKstrdup(value) : NULL;
649 : }
650 : break;
651 35641 : case 'R':
652 35641 : if (strcasecmp(property, "ClientRemark") == 0) {
653 35636 : GDKfree(c->client_remark);
654 35648 : c->client_remark = value ? GDKstrdup(value) : NULL;
655 : }
656 : break;
657 36847 : case 'P':
658 36847 : if (strcasecmp(property, "ClientPid") == 0 && value != NULL) {
659 36847 : char *end;
660 36847 : long n = strtol(value, &end, 10);
661 36842 : if (*value && !*end)
662 36847 : c->client_pid = n;
663 : }
664 : break;
665 : default:
666 : break;
667 : }
668 : }
|