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 37720 : MCnewClient(void)
147 : {
148 492579 : for (Client c = mal_clients; c < mal_clients + MAL_MAXCLIENTS; c++) {
149 492579 : if (c->idx == -1) {
150 37720 : assert(c->mode == FREECLIENT);
151 37720 : c->mode = RUNCLIENT;
152 37720 : c->idx = (int) (c - mal_clients);
153 37720 : 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 37719 : MCresetProfiler(stream *fdout)
186 : {
187 37719 : MT_lock_set(&mal_profileLock);
188 37719 : if (fdout == maleventstream) {
189 0 : maleventstream = NULL;
190 0 : profilerStatus = 0;
191 0 : profilerMode = 0;
192 : }
193 37719 : MT_lock_unset(&mal_profileLock);
194 37719 : }
195 :
196 : static void
197 37719 : MCexitClient(Client c)
198 : {
199 37719 : MCresetProfiler(c->fdout);
200 : // Remove any left over constant symbols
201 37719 : if (c->curprg)
202 327 : resetMalBlk(c->curprg->def);
203 37719 : if (c->father == NULL) { /* normal client */
204 37719 : if (c->fdout && c->fdout != GDKstdout)
205 37369 : close_stream(c->fdout);
206 37719 : assert(c->bak == NULL);
207 37719 : if (c->fdin) {
208 : /* protection against closing stdin stream */
209 37719 : if (c->fdin->s == GDKstdin)
210 350 : c->fdin->s = NULL;
211 37719 : bstream_destroy(c->fdin);
212 : }
213 37719 : c->fdout = NULL;
214 37719 : c->fdin = NULL;
215 37719 : c->qryctx.bs = NULL;
216 : }
217 37719 : assert(c->query == NULL);
218 37719 : 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 37719 : }
226 :
227 : static Client
228 37720 : MCinitClientRecord(Client c, oid user, bstream *fin, stream *fout)
229 : {
230 : /* mal_contextLock is held when this is called */
231 37720 : c->user = user;
232 37720 : c->username = 0;
233 37720 : c->scenario = NULL;
234 37720 : c->srcFile = NULL;
235 37720 : c->blkmode = 0;
236 :
237 37720 : c->fdin = fin ? fin : bstream_create(GDKstdin, 0);
238 37720 : 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 37720 : c->qryctx.bs = c->fdin;
245 37720 : c->yycur = 0;
246 37720 : c->bak = NULL;
247 :
248 37720 : c->listing = 0;
249 37720 : c->fdout = fout ? fout : GDKstdout;
250 37720 : c->curprg = c->backup = 0;
251 37720 : 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 37720 : c->usermodule = c->curmodule = 0;
257 :
258 37720 : c->father = NULL;
259 37720 : c->idle = c->login = c->lastcmd = time(0);
260 37720 : c->session = GDKusec();
261 37720 : strcpy_len(c->optimizer, "default_pipe", sizeof(c->optimizer));
262 37720 : c->workerlimit = 0;
263 37720 : c->memorylimit = 0;
264 37720 : c->querytimeout = 0;
265 37720 : c->sessiontimeout = 0;
266 37720 : c->logical_sessiontimeout = 0;
267 37720 : c->qryctx.starttime = 0;
268 37720 : c->qryctx.endtime = 0;
269 37720 : ATOMIC_SET(&c->qryctx.datasize, 0);
270 37720 : c->qryctx.maxmem = 0;
271 37720 : c->maxmem = 0;
272 37720 : c->errbuf = 0;
273 :
274 37720 : c->prompt = PROMPT1;
275 37720 : c->promptlength = strlen(c->prompt);
276 :
277 37720 : c->profticks = c->profstmt = c->profevents = NULL;
278 37720 : c->error_row = c->error_fld = c->error_msg = c->error_input = NULL;
279 37720 : c->sqlprofiler = 0;
280 37720 : c->blocksize = BLOCK;
281 37720 : c->protocol = PROTOCOL_9;
282 :
283 37720 : c->filetrans = false;
284 37720 : c->handshake_options = NULL;
285 37720 : c->query = NULL;
286 :
287 37720 : char name[MT_NAME_LEN];
288 37720 : snprintf(name, sizeof(name), "Client%d->s", (int) (c - mal_clients));
289 37720 : MT_sema_init(&c->s, 0, name);
290 37720 : return c;
291 : }
292 :
293 : Client
294 37719 : MCinitClient(oid user, bstream *fin, stream *fout)
295 : {
296 37719 : Client c = NULL;
297 :
298 37719 : MT_lock_set(&mal_contextLock);
299 37720 : c = MCnewClient();
300 37720 : if (c) {
301 37720 : c = MCinitClientRecord(c, user, fin, fout);
302 37720 : MT_thread_set_qry_ctx(&c->qryctx);
303 : }
304 37720 : MT_lock_unset(&mal_contextLock);
305 :
306 37719 : if (c && profilerStatus > 0)
307 0 : profilerEvent(NULL,
308 : &(struct NonMalEvent)
309 0 : { CLIENT_START, c, c->session, NULL, NULL, 0, 0 }
310 : );
311 37719 : 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 37669 : MCinitClientThread(Client c)
321 : {
322 : /*
323 : * The GDK thread administration should be set to reflect use of
324 : * the proper IO descriptors.
325 : */
326 37669 : c->mythread = MT_thread_getname();
327 37668 : c->errbuf = GDKerrbuf;
328 37670 : if (c->errbuf == NULL) {
329 37670 : char *n = GDKzalloc(GDKMAXERRLEN);
330 37682 : if (n == NULL) {
331 0 : MCresetProfiler(c->fdout);
332 0 : return -1;
333 : }
334 37682 : GDKsetbuf(n);
335 37678 : 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 37717 : MCcloseClient(Client c)
365 : {
366 37717 : MT_lock_set(&mal_contextLock);
367 37719 : if (c->mode == FREECLIENT) {
368 0 : assert(c->idx == -1);
369 0 : MT_lock_unset(&mal_contextLock);
370 0 : return;
371 : }
372 37719 : c->mode = FINISHCLIENT;
373 37719 : MT_lock_unset(&mal_contextLock);
374 :
375 37719 : 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 37719 : c->scenario = NULL;
381 37719 : c->prompt = NULL;
382 37719 : c->promptlength = -1;
383 37719 : if (c->errbuf) {
384 : /* no client threads in embedded mode */
385 37683 : GDKsetbuf(NULL);
386 37682 : if (c->father == NULL)
387 37682 : GDKfree(c->errbuf);
388 37683 : c->errbuf = NULL;
389 : }
390 37719 : if (c->usermodule)
391 330 : freeModule(c->usermodule);
392 37719 : c->usermodule = c->curmodule = 0;
393 37719 : c->father = 0;
394 37719 : strcpy_len(c->optimizer, "default_pipe", sizeof(c->optimizer));
395 37718 : c->workerlimit = 0;
396 37718 : c->memorylimit = 0;
397 37718 : c->querytimeout = 0;
398 37718 : c->qryctx.endtime = 0;
399 37718 : c->sessiontimeout = 0;
400 37718 : c->logical_sessiontimeout = 0;
401 37718 : c->user = oid_nil;
402 37718 : if (c->username) {
403 37366 : GDKfree(c->username);
404 37366 : c->username = 0;
405 : }
406 37718 : if (c->peer) {
407 37365 : GDKfree(c->peer);
408 37366 : c->peer = 0;
409 : }
410 37719 : if (c->client_hostname) {
411 36853 : GDKfree(c->client_hostname);
412 36853 : c->client_hostname = 0;
413 : }
414 37719 : if (c->client_application) {
415 36853 : GDKfree(c->client_application);
416 36853 : c->client_application = 0;
417 : }
418 37719 : if (c->client_library) {
419 36853 : GDKfree(c->client_library);
420 36853 : c->client_library = 0;
421 : }
422 37719 : if (c->client_remark) {
423 11 : GDKfree(c->client_remark);
424 11 : c->client_remark = 0;
425 : }
426 37719 : c->client_pid = 0;
427 37719 : c->mythread = NULL;
428 37719 : if (c->glb) {
429 37715 : freeStack(c->glb);
430 37715 : c->glb = NULL;
431 : }
432 37719 : 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 37719 : 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 37719 : c->sqlprofiler = 0;
446 37719 : free(c->handshake_options);
447 37719 : c->handshake_options = NULL;
448 37719 : MT_thread_set_qry_ctx(NULL);
449 37719 : assert(c->qryctx.datasize == 0);
450 37719 : MT_sema_destroy(&c->s);
451 37718 : MT_lock_set(&mal_contextLock);
452 37719 : c->idle = c->login = c->lastcmd = 0;
453 37719 : if (shutdowninprogress) {
454 32 : c->mode = BLOCKCLIENT;
455 : } else {
456 37687 : c->mode = FREECLIENT;
457 37687 : c->idx = -1;
458 : }
459 37719 : 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 47896 : MCactiveClients(void)
499 : {
500 47896 : int active = 0;
501 :
502 47896 : MT_lock_set(&mal_contextLock);
503 4337852 : for (Client cntxt = mal_clients; cntxt < mal_clients + MAL_MAXCLIENTS;
504 4289956 : cntxt++) {
505 8394862 : active += (cntxt->idle == 0 && cntxt->mode == RUNCLIENT);
506 : }
507 47896 : MT_lock_unset(&mal_contextLock);
508 47896 : 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 10994 : MCreadClient(Client c)
551 : {
552 10994 : bstream *in = c->fdin;
553 :
554 12396 : 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 10994 : if (in->pos >= in->len || in->mode) {
560 10994 : ssize_t rd;
561 :
562 10994 : if (in->eof || !isa_block_stream(c->fdout)) {
563 10994 : if (!isa_block_stream(c->fdout) && c->promptlength > 0)
564 0 : mnstr_write(c->fdout, c->prompt, c->promptlength, 1);
565 10993 : mnstr_flush(c->fdout, MNSTR_FLUSH_DATA);
566 10986 : in->eof = false;
567 : }
568 21470 : while ((rd = bstream_next(in)) > 0 && !in->eof) {
569 10484 : if (!in->mode) /* read one line at a time in line mode */
570 : break;
571 : }
572 10993 : if (rd < 0) {
573 : /* force end of stream handling below */
574 0 : in->pos = in->len;
575 10993 : } else if (in->mode) { /* find last new line */
576 10985 : char *p = in->buf + in->len - 1;
577 :
578 10985 : while (p > in->buf && *p != '\n') {
579 0 : *(p + 1) = *p;
580 0 : p--;
581 : }
582 10985 : if (p > in->buf)
583 10494 : *(p + 1) = 0;
584 10985 : if (p != in->buf + in->len - 1)
585 0 : in->len++;
586 : }
587 : }
588 10993 : if (in->pos >= in->len) {
589 : /* end of stream reached */
590 503 : if (c->bak) {
591 4 : 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 183018 : MCsetClientInfo(Client c, const char *property, const char *value)
620 : {
621 183018 : 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 183018 : int discriminant = toupper(property[6]);
631 :
632 183018 : switch (discriminant) {
633 36830 : case 'H':
634 36830 : if (strcasecmp(property, "ClientHostname") == 0) {
635 36846 : GDKfree(c->client_hostname);
636 73676 : c->client_hostname = value ? GDKstrdup(value) : NULL;
637 : }
638 : break;
639 36838 : case 'A':
640 36838 : if (strcasecmp(property, "ApplicationName") == 0) {
641 36843 : GDKfree(c->client_application);
642 73691 : c->client_application = value ? GDKstrdup(value) : NULL;
643 : }
644 : break;
645 36852 : case 'L':
646 36852 : if (strcasecmp(property, "ClientLibrary") == 0) {
647 36850 : GDKfree(c->client_library);
648 73699 : c->client_library = value ? GDKstrdup(value) : NULL;
649 : }
650 : break;
651 35644 : case 'R':
652 35644 : if (strcasecmp(property, "ClientRemark") == 0) {
653 35643 : GDKfree(c->client_remark);
654 35654 : c->client_remark = value ? GDKstrdup(value) : NULL;
655 : }
656 : break;
657 36854 : case 'P':
658 36854 : if (strcasecmp(property, "ClientPid") == 0 && value != NULL) {
659 36852 : char *end;
660 36852 : long n = strtol(value, &end, 10);
661 36843 : if (*value && !*end)
662 36850 : c->client_pid = n;
663 : }
664 : break;
665 : default:
666 : break;
667 : }
668 : }
|