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 325 : mal_client_reset(void)
62 : {
63 325 : if (mal_clients) {
64 325 : GDKfree(mal_clients);
65 325 : mal_clients = NULL;
66 : }
67 325 : MAL_MAXCLIENTS = 0;
68 325 : }
69 :
70 : bool
71 327 : MCinit(void)
72 : {
73 327 : const char *max_clients = GDKgetenv("max_clients");
74 327 : int maxclients = 0;
75 :
76 327 : if (max_clients != NULL)
77 2 : maxclients = atoi(max_clients);
78 2 : if (maxclients <= 0) {
79 325 : maxclients = 64;
80 325 : 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 327 : MAL_MAXCLIENTS = /* client connections */ maxclients;
88 327 : mal_clients = GDKzalloc(sizeof(ClientRec) * MAL_MAXCLIENTS);
89 327 : if (mal_clients == NULL) {
90 0 : TRC_CRITICAL(MAL_SERVER,
91 : "Initialization failed: " MAL_MALLOC_FAIL "\n");
92 0 : return false;
93 : }
94 23179 : for (int i = 0; i < MAL_MAXCLIENTS; i++) {
95 22852 : ATOMIC_INIT(&mal_clients[i].lastprint, 0);
96 22852 : ATOMIC_INIT(&mal_clients[i].workers, 1);
97 22852 : ATOMIC_INIT(&mal_clients[i].qryctx.datasize, 0);
98 22852 : 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 38798 : MCnewClient(void)
147 : {
148 484202 : for (Client c = mal_clients; c < mal_clients + MAL_MAXCLIENTS; c++) {
149 484202 : if (c->idx == -1) {
150 38798 : assert(c->mode == FREECLIENT);
151 38798 : c->mode = RUNCLIENT;
152 38798 : c->idx = (int) (c - mal_clients);
153 38798 : 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 367753 : MCgetClient(int id)
173 : {
174 367753 : if (id <0 || id >=MAL_MAXCLIENTS)
175 : return NULL;
176 367753 : 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 38797 : MCresetProfiler(stream *fdout)
186 : {
187 38797 : MT_lock_set(&mal_profileLock);
188 38797 : if (fdout == maleventstream) {
189 0 : maleventstream = NULL;
190 0 : profilerStatus = 0;
191 0 : profilerMode = 0;
192 : }
193 38797 : MT_lock_unset(&mal_profileLock);
194 38797 : }
195 :
196 : static void
197 38797 : MCexitClient(Client c)
198 : {
199 38797 : MCresetProfiler(c->fdout);
200 : // Remove any left over constant symbols
201 38797 : if (c->curprg)
202 326 : resetMalBlk(c->curprg->def);
203 38797 : if (c->father == NULL) { /* normal client */
204 38797 : if (c->fdout && c->fdout != GDKstdout)
205 38448 : close_stream(c->fdout);
206 38797 : assert(c->bak == NULL);
207 38797 : if (c->fdin) {
208 : /* protection against closing stdin stream */
209 38797 : if (c->fdin->s == GDKstdin)
210 349 : c->fdin->s = NULL;
211 38797 : bstream_destroy(c->fdin);
212 : }
213 38797 : c->fdout = NULL;
214 38797 : c->fdin = NULL;
215 38797 : c->qryctx.bs = NULL;
216 : }
217 38797 : assert(c->query == NULL);
218 38797 : 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 38797 : }
226 :
227 : static Client
228 38798 : MCinitClientRecord(Client c, oid user, bstream *fin, stream *fout)
229 : {
230 : /* mal_contextLock is held when this is called */
231 38798 : c->user = user;
232 38798 : c->username = 0;
233 38798 : c->scenario = NULL;
234 38798 : c->srcFile = NULL;
235 38798 : c->blkmode = 0;
236 :
237 38798 : c->fdin = fin ? fin : bstream_create(GDKstdin, 0);
238 38798 : 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 38798 : c->qryctx.bs = c->fdin;
245 38798 : c->yycur = 0;
246 38798 : c->bak = NULL;
247 :
248 38798 : c->listing = 0;
249 38798 : c->fdout = fout ? fout : GDKstdout;
250 38798 : c->curprg = c->backup = 0;
251 38798 : 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 38798 : c->usermodule = c->curmodule = 0;
257 :
258 38798 : c->father = NULL;
259 38798 : c->idle = c->login = c->lastcmd = time(0);
260 38798 : c->session = GDKusec();
261 38798 : strcpy_len(c->optimizer, "default_pipe", sizeof(c->optimizer));
262 38798 : c->workerlimit = 0;
263 38798 : c->memorylimit = 0;
264 38798 : c->querytimeout = 0;
265 38798 : c->sessiontimeout = 0;
266 38798 : c->logical_sessiontimeout = 0;
267 38798 : c->qryctx.starttime = 0;
268 38798 : c->qryctx.endtime = 0;
269 38798 : ATOMIC_SET(&c->qryctx.datasize, 0);
270 38798 : c->qryctx.maxmem = 0;
271 38798 : c->maxmem = 0;
272 38798 : c->errbuf = 0;
273 :
274 38798 : c->prompt = PROMPT1;
275 38798 : c->promptlength = strlen(c->prompt);
276 :
277 38798 : c->profticks = c->profstmt = c->profevents = NULL;
278 38798 : c->error_row = c->error_fld = c->error_msg = c->error_input = NULL;
279 38798 : c->sqlprofiler = 0;
280 38798 : c->blocksize = BLOCK;
281 38798 : c->protocol = PROTOCOL_9;
282 :
283 38798 : c->filetrans = false;
284 38798 : c->handshake_options = NULL;
285 38798 : c->query = NULL;
286 :
287 38798 : char name[MT_NAME_LEN];
288 38798 : snprintf(name, sizeof(name), "Client%d->s", (int) (c - mal_clients));
289 38798 : MT_sema_init(&c->s, 0, name);
290 38798 : return c;
291 : }
292 :
293 : Client
294 38798 : MCinitClient(oid user, bstream *fin, stream *fout)
295 : {
296 38798 : Client c = NULL;
297 :
298 38798 : MT_lock_set(&mal_contextLock);
299 38798 : c = MCnewClient();
300 38798 : if (c) {
301 38798 : c = MCinitClientRecord(c, user, fin, fout);
302 38798 : MT_thread_set_qry_ctx(&c->qryctx);
303 : }
304 38798 : MT_lock_unset(&mal_contextLock);
305 :
306 38798 : if (c && profilerStatus > 0)
307 0 : profilerEvent(NULL,
308 : &(struct NonMalEvent)
309 0 : { CLIENT_START, c, c->session, NULL, NULL, 0, 0 }
310 : );
311 38798 : 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 38762 : MCinitClientThread(Client c)
321 : {
322 : /*
323 : * The GDK thread administration should be set to reflect use of
324 : * the proper IO descriptors.
325 : */
326 38762 : c->mythread = MT_thread_getname();
327 38762 : c->errbuf = GDKerrbuf;
328 38762 : if (c->errbuf == NULL) {
329 38762 : char *n = GDKzalloc(GDKMAXERRLEN);
330 38761 : if (n == NULL) {
331 0 : MCresetProfiler(c->fdout);
332 0 : return -1;
333 : }
334 38761 : GDKsetbuf(n);
335 38762 : 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 38797 : MCcloseClient(Client c)
365 : {
366 38797 : MT_lock_set(&mal_contextLock);
367 38797 : if (c->mode == FREECLIENT) {
368 0 : assert(c->idx == -1);
369 0 : MT_lock_unset(&mal_contextLock);
370 0 : return;
371 : }
372 38797 : c->mode = FINISHCLIENT;
373 38797 : MT_lock_unset(&mal_contextLock);
374 :
375 38797 : 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 38797 : c->scenario = NULL;
381 38797 : c->prompt = NULL;
382 38797 : c->promptlength = -1;
383 38797 : if (c->errbuf) {
384 : /* no client threads in embedded mode */
385 38761 : GDKsetbuf(NULL);
386 38761 : if (c->father == NULL)
387 38761 : GDKfree(c->errbuf);
388 38761 : c->errbuf = NULL;
389 : }
390 38797 : if (c->usermodule)
391 329 : freeModule(c->usermodule);
392 38797 : c->usermodule = c->curmodule = 0;
393 38797 : c->father = 0;
394 38797 : strcpy_len(c->optimizer, "default_pipe", sizeof(c->optimizer));
395 38797 : c->workerlimit = 0;
396 38797 : c->memorylimit = 0;
397 38797 : c->querytimeout = 0;
398 38797 : c->qryctx.endtime = 0;
399 38797 : c->sessiontimeout = 0;
400 38797 : c->logical_sessiontimeout = 0;
401 38797 : c->user = oid_nil;
402 38797 : if (c->username) {
403 38445 : GDKfree(c->username);
404 38445 : c->username = 0;
405 : }
406 38797 : if (c->peer) {
407 38445 : GDKfree(c->peer);
408 38445 : c->peer = 0;
409 : }
410 38797 : if (c->client_hostname) {
411 37932 : GDKfree(c->client_hostname);
412 37932 : c->client_hostname = 0;
413 : }
414 38797 : if (c->client_application) {
415 37932 : GDKfree(c->client_application);
416 37932 : c->client_application = 0;
417 : }
418 38797 : if (c->client_library) {
419 37932 : GDKfree(c->client_library);
420 37932 : c->client_library = 0;
421 : }
422 38797 : if (c->client_remark) {
423 11 : GDKfree(c->client_remark);
424 11 : c->client_remark = 0;
425 : }
426 38797 : c->client_pid = 0;
427 38797 : c->mythread = NULL;
428 38797 : if (c->glb) {
429 38793 : freeStack(c->glb);
430 38793 : c->glb = NULL;
431 : }
432 38797 : 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 38797 : if (c->error_row) {
439 483 : BBPunfix(c->error_row->batCacheid);
440 483 : BBPunfix(c->error_fld->batCacheid);
441 483 : BBPunfix(c->error_msg->batCacheid);
442 483 : BBPunfix(c->error_input->batCacheid);
443 483 : c->error_row = c->error_fld = c->error_msg = c->error_input = NULL;
444 : }
445 38797 : c->sqlprofiler = 0;
446 38797 : free(c->handshake_options);
447 38797 : c->handshake_options = NULL;
448 38797 : MT_thread_set_qry_ctx(NULL);
449 38797 : assert(c->qryctx.datasize == 0);
450 38797 : MT_sema_destroy(&c->s);
451 38797 : MT_lock_set(&mal_contextLock);
452 38797 : c->idle = c->login = c->lastcmd = 0;
453 38797 : if (shutdowninprogress) {
454 32 : c->mode = BLOCKCLIENT;
455 : } else {
456 38765 : c->mode = FREECLIENT;
457 38765 : c->idx = -1;
458 : }
459 38797 : 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 327 : MCstopClients(Client cntxt)
479 : {
480 327 : MT_lock_set(&mal_contextLock);
481 23179 : for (int i = 0; i < MAL_MAXCLIENTS; i++) {
482 22852 : Client c = mal_clients + i;
483 22852 : if (cntxt != c) {
484 22850 : if (c->mode == RUNCLIENT)
485 0 : c->mode = FINISHCLIENT;
486 22850 : else if (c->mode == FREECLIENT) {
487 22695 : assert(c->idx == -1);
488 22695 : c->idx = i;
489 22695 : c->mode = BLOCKCLIENT;
490 : }
491 : }
492 : }
493 327 : shutdowninprogress = true;
494 327 : MT_lock_unset(&mal_contextLock);
495 327 : }
496 :
497 : int
498 47568 : MCactiveClients(void)
499 : {
500 47568 : int active = 0;
501 :
502 47568 : MT_lock_set(&mal_contextLock);
503 4149876 : for (Client cntxt = mal_clients; cntxt < mal_clients + MAL_MAXCLIENTS;
504 4102308 : cntxt++) {
505 8019686 : active += (cntxt->idle == 0 && cntxt->mode == RUNCLIENT);
506 : }
507 47568 : MT_lock_unset(&mal_contextLock);
508 47568 : 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 10995 : in->eof = false;
567 : }
568 21491 : while ((rd = bstream_next(in)) > 0 && !in->eof) {
569 10496 : if (!in->mode) /* read one line at a time in line mode */
570 : break;
571 : }
572 10995 : if (rd < 0) {
573 : /* force end of stream handling below */
574 0 : in->pos = in->len;
575 10995 : } else if (in->mode) { /* find last new line */
576 10995 : char *p = in->buf + in->len - 1;
577 :
578 10995 : while (p > in->buf && *p != '\n') {
579 0 : *(p + 1) = *p;
580 0 : p--;
581 : }
582 10995 : if (p > in->buf)
583 10496 : *(p + 1) = 0;
584 10995 : if (p != in->buf + in->len - 1)
585 0 : in->len++;
586 : }
587 : }
588 10995 : if (in->pos >= in->len) {
589 : /* end of stream reached */
590 499 : if (c->bak) {
591 0 : 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 188456 : MCsetClientInfo(Client c, const char *property, const char *value)
620 : {
621 188456 : 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 188456 : int discriminant = toupper(property[6]);
631 :
632 188456 : switch (discriminant) {
633 37933 : case 'H':
634 37933 : if (strcasecmp(property, "ClientHostname") == 0) {
635 37933 : GDKfree(c->client_hostname);
636 75864 : c->client_hostname = value ? GDKstrdup(value) : NULL;
637 : }
638 : break;
639 37933 : case 'A':
640 37933 : if (strcasecmp(property, "ApplicationName") == 0) {
641 37933 : GDKfree(c->client_application);
642 75864 : c->client_application = value ? GDKstrdup(value) : NULL;
643 : }
644 : break;
645 37933 : case 'L':
646 37933 : if (strcasecmp(property, "ClientLibrary") == 0) {
647 37933 : GDKfree(c->client_library);
648 75865 : c->client_library = value ? GDKstrdup(value) : NULL;
649 : }
650 : break;
651 36724 : case 'R':
652 36724 : if (strcasecmp(property, "ClientRemark") == 0) {
653 36723 : GDKfree(c->client_remark);
654 36734 : c->client_remark = value ? GDKstrdup(value) : NULL;
655 : }
656 : break;
657 37933 : case 'P':
658 37933 : if (strcasecmp(property, "ClientPid") == 0 && value != NULL) {
659 37932 : char *end;
660 37932 : long n = strtol(value, &end, 10);
661 37931 : if (*value && !*end)
662 37931 : c->client_pid = n;
663 : }
664 : break;
665 : default:
666 : break;
667 : }
668 : }
|