VirtualBox

source: vbox/trunk/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp@ 78235

Last change on this file since 78235 was 78179, checked in by vboxsync, 6 years ago

Shared Clipboard: Coding style.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.2 KB
Line 
1/* $Id: VBoxSharedClipboardSvc-x11.cpp 78179 2019-04-17 19:06:26Z vboxsync $ */
2/** @file
3 * Shared Clipboard Service - Linux host.
4 */
5
6/*
7 * Copyright (C) 2006-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
23#include <iprt/assert.h>
24#include <iprt/critsect.h>
25#include <iprt/env.h>
26#include <iprt/mem.h>
27#include <iprt/semaphore.h>
28#include <iprt/string.h>
29
30#include <VBox/GuestHost/SharedClipboard.h>
31#include <VBox/HostServices/VBoxClipboardSvc.h>
32#include <VBox/err.h>
33
34#include "VBoxClipboard.h"
35
36
37/*********************************************************************************************************************************
38* Structures and Typedefs *
39*********************************************************************************************************************************/
40struct _VBOXCLIPBOARDREQFROMVBOX;
41typedef struct _VBOXCLIPBOARDREQFROMVBOX VBOXCLIPBOARDREQFROMVBOX;
42
43/** Global context information used by the host glue for the X11 clipboard
44 * backend */
45struct _VBOXCLIPBOARDCONTEXT
46{
47 /** This mutex is grabbed during any critical operations on the clipboard
48 * which might clash with others. */
49 RTCRITSECT clipboardMutex;
50 /** The currently pending request for data from VBox. NULL if there is
51 * no request pending. The protocol for completing a request is to grab
52 * the critical section, check that @a pReq is not NULL, fill in the data
53 * fields and set @a pReq to NULL. The protocol for cancelling a pending
54 * request is to grab the critical section and set pReq to NULL.
55 * It is an error if a request arrives while another one is pending, and
56 * the backend is responsible for ensuring that this does not happen. */
57 VBOXCLIPBOARDREQFROMVBOX *pReq;
58 /** Pointer to the opaque X11 backend structure */
59 CLIPBACKEND *pBackend;
60 /** Pointer to the VBox host client data structure. */
61 VBOXCLIPBOARDCLIENTDATA *pClient;
62 /** We set this when we start shutting down as a hint not to post any new
63 * requests. */
64 bool fShuttingDown;
65};
66
67
68
69/**
70 * Report formats available in the X11 clipboard to VBox.
71 * @param pCtx Opaque context pointer for the glue code
72 * @param u32Formats The formats available
73 * @note Host glue code
74 */
75void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Formats)
76{
77 LogRelFlowFunc(("called. pCtx=%p, u32Formats=%02X\n", pCtx, u32Formats));
78 vboxSvcClipboardReportMsg(pCtx->pClient,
79 VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS,
80 u32Formats);
81}
82
83/**
84 * Initialise the host side of the shared clipboard.
85 * @note Host glue code
86 */
87int vboxClipboardInit(void)
88{
89 return VINF_SUCCESS;
90}
91
92/**
93 * Terminate the host side of the shared clipboard.
94 * @note host glue code
95 */
96void vboxClipboardDestroy(void)
97{
98
99}
100
101/**
102 * Connect a guest to the shared clipboard.
103 * @note host glue code
104 * @note on the host, we assume that some other application already owns
105 * the clipboard and leave ownership to X11.
106 */
107int vboxClipboardConnect(VBOXCLIPBOARDCLIENTDATA *pClient, bool fHeadless)
108{
109 int rc = VINF_SUCCESS;
110 CLIPBACKEND *pBackend = NULL;
111
112 LogRel(("Starting host clipboard service\n"));
113 VBOXCLIPBOARDCONTEXT *pCtx = (VBOXCLIPBOARDCONTEXT *)RTMemAllocZ(sizeof(VBOXCLIPBOARDCONTEXT));
114 if (!pCtx)
115 rc = VERR_NO_MEMORY;
116 else
117 {
118 RTCritSectInit(&pCtx->clipboardMutex);
119 pBackend = ClipConstructX11(pCtx, fHeadless);
120 if (pBackend == NULL)
121 rc = VERR_NO_MEMORY;
122 else
123 {
124 pCtx->pBackend = pBackend;
125 pClient->pCtx = pCtx;
126 pCtx->pClient = pClient;
127 rc = ClipStartX11(pBackend, true /* grab shared clipboard */);
128 }
129 if (RT_FAILURE(rc))
130 RTCritSectDelete(&pCtx->clipboardMutex);
131 }
132 if (RT_FAILURE(rc))
133 {
134 RTMemFree(pCtx);
135 LogRel(("Failed to initialise the shared clipboard\n"));
136 }
137 LogRelFlowFunc(("returning %Rrc\n", rc));
138 return rc;
139}
140
141/**
142 * Synchronise the contents of the host clipboard with the guest, called
143 * after a save and restore of the guest.
144 * @note Host glue code
145 */
146int vboxClipboardSync(VBOXCLIPBOARDCLIENTDATA *pClient)
147{
148 /* Tell the guest we have no data in case X11 is not available. If
149 * there is data in the host clipboard it will automatically be sent to
150 * the guest when the clipboard starts up. */
151 vboxSvcClipboardReportMsg(pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, 0);
152 /** @todo r=andy Check rc code. */
153 return VINF_SUCCESS;
154}
155
156/**
157 * Shut down the shared clipboard service and "disconnect" the guest.
158 * @note Host glue code
159 */
160void vboxClipboardDisconnect(VBOXCLIPBOARDCLIENTDATA *pClient)
161{
162 LogRelFlow(("vboxClipboardDisconnect\n"));
163
164 LogRel(("Stopping the host clipboard service\n"));
165 VBOXCLIPBOARDCONTEXT *pCtx = pClient->pCtx;
166 /* Drop the reference to the client, in case it is still there. This
167 * will cause any outstanding clipboard data requests from X11 to fail
168 * immediately. */
169 pCtx->fShuttingDown = true;
170 /* If there is a currently pending request, release it immediately. */
171 vboxClipboardWriteData(pClient, NULL, 0, 0);
172 int rc = ClipStopX11(pCtx->pBackend);
173 /** @todo handle this slightly more reasonably, or be really sure
174 * it won't go wrong. */
175 AssertRC(rc);
176 if (RT_SUCCESS(rc)) /* And if not? */
177 {
178 ClipDestructX11(pCtx->pBackend);
179 RTCritSectDelete(&pCtx->clipboardMutex);
180 RTMemFree(pCtx);
181 }
182}
183
184/**
185 * VBox is taking possession of the shared clipboard.
186 *
187 * @param pClient Context data for the guest system
188 * @param u32Formats Clipboard formats the guest is offering
189 * @note Host glue code
190 */
191void vboxClipboardFormatAnnounce(VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Formats)
192{
193 LogRelFlowFunc(("called. pClient=%p, u32Formats=%02X\n", pClient, u32Formats));
194
195 ClipAnnounceFormatToX11(pClient->pCtx->pBackend, u32Formats);
196}
197
198/** Structure describing a request for clipoard data from the guest. */
199struct _CLIPREADCBREQ
200{
201 /** Where to write the returned data to. */
202 void *pv;
203 /** The size of the buffer in pv */
204 uint32_t cb;
205 /** The actual size of the data written */
206 uint32_t *pcbActual;
207};
208
209/**
210 * Called when VBox wants to read the X11 clipboard.
211 *
212 * @returns VINF_SUCCESS on successful completion
213 * @returns VINF_HGCM_ASYNC_EXECUTE if the operation will complete
214 * asynchronously
215 * @returns iprt status code on failure
216 * @param pClient Context information about the guest VM
217 * @param u32Format The format that the guest would like to receive the data in
218 * @param pv Where to write the data to
219 * @param cb The size of the buffer to write the data to
220 * @param pcbActual Where to write the actual size of the written data
221 * @note We always fail or complete asynchronously
222 * @note On success allocates a CLIPREADCBREQ structure which must be
223 * freed in ClipCompleteDataRequestFromX11 when it is called back from
224 * the backend code.
225 *
226 */
227int vboxClipboardReadData(VBOXCLIPBOARDCLIENTDATA *pClient,
228 uint32_t u32Format, void *pv, uint32_t cb,
229 uint32_t *pcbActual)
230{
231 LogRelFlowFunc(("pClient=%p, u32Format=%02X, pv=%p, cb=%u, pcbActual=%p",
232 pClient, u32Format, pv, cb, pcbActual));
233
234 int rc = VINF_SUCCESS;
235 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)RTMemAlloc(sizeof(CLIPREADCBREQ));
236 if (!pReq)
237 rc = VERR_NO_MEMORY;
238 else
239 {
240 pReq->pv = pv;
241 pReq->cb = cb;
242 pReq->pcbActual = pcbActual;
243 rc = ClipRequestDataFromX11(pClient->pCtx->pBackend, u32Format, pReq);
244 if (RT_SUCCESS(rc))
245 rc = VINF_HGCM_ASYNC_EXECUTE;
246 }
247 LogRelFlowFunc(("returning %Rrc\n", rc));
248 return rc;
249}
250
251/**
252 * Complete a request from VBox for the X11 clipboard data. The data should
253 * be written to the buffer provided in the initial request.
254 * @param pCtx request context information
255 * @param rc the completion status of the request
256 * @param pReq request
257 * @param pv address
258 * @param cb size
259 *
260 * @todo change this to deal with the buffer issues rather than offloading
261 * them onto the caller
262 */
263void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc,
264 CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
265{
266 if (cb <= pReq->cb && cb != 0)
267 memcpy(pReq->pv, pv, cb);
268
269 RTMemFree(pReq);
270
271 vboxSvcClipboardCompleteReadData(pCtx->pClient, rc, cb);
272}
273
274/** A request for clipboard data from VBox */
275struct _VBOXCLIPBOARDREQFROMVBOX
276{
277 /** Data received */
278 void *pv;
279 /** The size of the data */
280 uint32_t cb;
281 /** Format of the data */
282 uint32_t format;
283 /** A semaphore for waiting for the data */
284 RTSEMEVENT finished;
285};
286
287/** Wait for clipboard data requested from VBox to arrive. */
288static int clipWaitForDataFromVBox(VBOXCLIPBOARDCONTEXT *pCtx,
289 VBOXCLIPBOARDREQFROMVBOX *pReq,
290 uint32_t u32Format)
291{
292 int rc = VINF_SUCCESS;
293
294 LogRelFlowFunc(("pCtx=%p, pReq=%p, u32Format=%02X\n", pCtx, pReq, u32Format));
295
296 /* Request data from VBox */
297 vboxSvcClipboardReportMsg(pCtx->pClient,
298 VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
299 u32Format);
300 /* Which will signal us when it is ready. We use a timeout here
301 * because we can't be sure that the guest will behave correctly.
302 */
303 rc = RTSemEventWait(pReq->finished, CLIPBOARD_TIMEOUT);
304 /* If the request hasn't yet completed then we cancel it. We use
305 * the critical section to prevent these operations colliding. */
306 RTCritSectEnter(&pCtx->clipboardMutex);
307 /* The data may have arrived between the semaphore timing out and
308 * our grabbing the mutex. */
309 if (rc == VERR_TIMEOUT && pReq->pv != NULL)
310 rc = VINF_SUCCESS;
311 if (pCtx->pReq == pReq)
312 pCtx->pReq = NULL;
313 Assert(pCtx->pReq == NULL);
314 RTCritSectLeave(&pCtx->clipboardMutex);
315 if (RT_SUCCESS(rc) && (pReq->pv == NULL))
316 rc = VERR_NO_DATA;
317 LogRelFlowFunc(("returning %Rrc\n", rc));
318 return rc;
319}
320
321/** Post a request for clipboard data to VBox/the guest and wait for it to be
322 * completed. */
323static int clipRequestDataFromVBox(VBOXCLIPBOARDCONTEXT *pCtx, VBOXCLIPBOARDREQFROMVBOX *pReq, uint32_t u32Format)
324{
325 int rc = VINF_SUCCESS;
326
327 LogRelFlowFunc(("pCtx=%p, pReq=%p, u32Format=%02X\n", pCtx, pReq, u32Format));
328
329 /* Start by "posting" the request for the next invocation of
330 * vboxClipboardWriteData. */
331 RTCritSectEnter(&pCtx->clipboardMutex);
332 if (pCtx->pReq != NULL)
333 {
334 /* This would be a violation of the protocol, see the comments in the
335 * context structure definition. */
336 Assert(false);
337 rc = VERR_WRONG_ORDER;
338 }
339 else
340 pCtx->pReq = pReq;
341 RTCritSectLeave(&pCtx->clipboardMutex);
342 if (RT_SUCCESS(rc))
343 rc = clipWaitForDataFromVBox(pCtx, pReq, u32Format);
344 LogRelFlowFunc(("returning %Rrc\n", rc));
345 return rc;
346}
347
348/**
349 * Send a request to VBox to transfer the contents of its clipboard to X11.
350 *
351 * @param pCtx Pointer to the host clipboard structure
352 * @param u32Format The format in which the data should be transferred
353 * @param ppv On success and if pcb > 0, this will point to a buffer
354 * to be freed with RTMemFree containing the data read.
355 * @param pcb On success, this contains the number of bytes of data
356 * returned
357 * @note Host glue code.
358 */
359int ClipRequestDataForX11(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Format, void **ppv, uint32_t *pcb)
360{
361 VBOXCLIPBOARDREQFROMVBOX request = { NULL, 0, 0, NIL_RTSEMEVENT };
362
363 LogRelFlowFunc(("pCtx=%p, u32Format=%02X, ppv=%p, pcb=%p\n", pCtx, u32Format, ppv, pcb));
364
365 if (pCtx->fShuttingDown)
366 {
367 /* The shared clipboard is disconnecting. */
368 LogRelFunc(("host requested guest clipboard data after guest had disconnected.\n"));
369 return VERR_WRONG_ORDER;
370 }
371 int rc = RTSemEventCreate(&request.finished);
372 if (RT_SUCCESS(rc))
373 {
374 rc = clipRequestDataFromVBox(pCtx, &request, u32Format);
375 RTSemEventDestroy(request.finished);
376 }
377
378 if (RT_SUCCESS(rc))
379 {
380 *ppv = request.pv;
381 *pcb = request.cb;
382 }
383
384 LogRelFlowFunc(("returning %Rrc\n", rc));
385
386 if (RT_SUCCESS(rc))
387 LogRelFlowFunc(("*ppv=%.*ls, *pcb=%u\n",*pcb / 2,*ppv,*pcb));
388
389 return rc;
390}
391
392/**
393 * Called when we have requested data from VBox and that data has arrived.
394 *
395 * @param pClient Context information about the guest VM
396 * @param pv Buffer to which the data was written
397 * @param cb The size of the data written
398 * @param u32Format The format of the data written
399 * @note Host glue code
400 */
401void vboxClipboardWriteData(VBOXCLIPBOARDCLIENTDATA *pClient,
402 void *pv, uint32_t cb, uint32_t u32Format)
403{
404 LogRelFlowFunc(("called. pClient=%p, pv=%p (%.*ls), cb=%u, u32Format=%02X\n",
405 pClient, pv, cb / 2, pv, cb, u32Format));
406
407 VBOXCLIPBOARDCONTEXT *pCtx = pClient->pCtx;
408
409 /* Grab the mutex and check whether there is a pending request for data. */
410 RTCritSectEnter(&pCtx->clipboardMutex);
411
412 VBOXCLIPBOARDREQFROMVBOX *pReq = pCtx->pReq;
413 if (pReq != NULL)
414 {
415 if (cb > 0)
416 {
417 pReq->pv = RTMemDup(pv, cb);
418 if (pReq->pv != NULL) /* NULL may also mean no memory... */
419 {
420 pReq->cb = cb;
421 pReq->format = u32Format;
422 }
423 }
424 /* Signal that the request has been completed. */
425 RTSemEventSignal(pReq->finished);
426 pCtx->pReq = NULL;
427 }
428
429 RTCritSectLeave(&pCtx->clipboardMutex);
430}
431
432#ifdef TESTCASE
433# include <iprt/initterm.h>
434# include <iprt/stream.h>
435
436# define TEST_NAME "tstClipboardX11-2"
437
438struct _CLIPBACKEND
439{
440 uint32_t formats;
441 struct _READDATA
442 {
443 uint32_t format;
444 int rc;
445 CLIPREADCBREQ *pReq;
446 } readData;
447 struct _COMPLETEREAD
448 {
449 int rc;
450 uint32_t cbActual;
451 } completeRead;
452 struct _WRITEDATA
453 {
454 void *pv;
455 uint32_t cb;
456 uint32_t format;
457 bool timeout;
458 } writeData;
459 struct _REPORTDATA
460 {
461 uint32_t format;
462 } reportData;
463};
464
465void vboxSvcClipboardReportMsg(VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Msg, uint32_t u32Formats)
466{
467 RT_NOREF(u32Formats);
468 CLIPBACKEND *pBackend = pClient->pCtx->pBackend;
469
470 if ((u32Msg == VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA)
471 && !pBackend->writeData.timeout)
472 vboxClipboardWriteData(pClient, pBackend->writeData.pv,
473 pBackend->writeData.cb,
474 pBackend->writeData.format);
475 else
476 return;
477}
478
479void vboxSvcClipboardCompleteReadData(VBOXCLIPBOARDCLIENTDATA *pClient, int rc, uint32_t cbActual)
480{
481 CLIPBACKEND *pBackend = pClient->pCtx->pBackend;
482 pBackend->completeRead.rc = rc;
483 pBackend->completeRead.cbActual = cbActual;
484}
485
486CLIPBACKEND* ClipConstructX11(VBOXCLIPBOARDCONTEXT *pFrontend, bool)
487{
488 RT_NOREF(pFrontend);
489 return (CLIPBACKEND *)RTMemAllocZ(sizeof(CLIPBACKEND));
490}
491
492void ClipDestructX11(CLIPBACKEND *pBackend)
493{
494 RTMemFree(pBackend);
495}
496
497int ClipStartX11(CLIPBACKEND *pBackend, bool)
498{
499 RT_NOREF(pBackend);
500 return VINF_SUCCESS;
501}
502
503int ClipStopX11(CLIPBACKEND *pBackend)
504{
505 RT_NOREF1(pBackend);
506 return VINF_SUCCESS;
507}
508
509void ClipAnnounceFormatToX11(CLIPBACKEND *pBackend, uint32_t u32Formats)
510{
511 pBackend->formats = u32Formats;
512}
513
514extern int ClipRequestDataFromX11(CLIPBACKEND *pBackend, uint32_t u32Format,
515 CLIPREADCBREQ *pReq)
516{
517 pBackend->readData.format = u32Format;
518 pBackend->readData.pReq = pReq;
519 return pBackend->readData.rc;
520}
521
522int main()
523{
524 VBOXCLIPBOARDCLIENTDATA client;
525 unsigned cErrors = 0;
526 int rc = RTR3InitExeNoArguments(0);
527 RTPrintf(TEST_NAME ": TESTING\n");
528 AssertRCReturn(rc, 1);
529 rc = vboxClipboardConnect(&client, false);
530 CLIPBACKEND *pBackend = client.pCtx->pBackend;
531 AssertRCReturn(rc, 1);
532 vboxClipboardFormatAnnounce(&client,
533 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
534 if (pBackend->formats != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
535 {
536 RTPrintf(TEST_NAME ": vboxClipboardFormatAnnounce failed with VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT\n");
537 ++cErrors;
538 }
539 pBackend->readData.rc = VINF_SUCCESS;
540 client.asyncRead.callHandle = (VBOXHGCMCALLHANDLE)pBackend;
541 client.asyncRead.paParms = (VBOXHGCMSVCPARM *)&client;
542 uint32_t u32Dummy;
543 rc = vboxClipboardReadData(&client, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
544 &u32Dummy, 42, &u32Dummy);
545 if (rc != VINF_HGCM_ASYNC_EXECUTE)
546 {
547 RTPrintf(TEST_NAME ": vboxClipboardReadData returned %Rrc\n", rc);
548 ++cErrors;
549 }
550 else
551 {
552 if ( pBackend->readData.format != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT
553 || pBackend->readData.pReq->pv != &u32Dummy
554 || pBackend->readData.pReq->cb != 42
555 || pBackend->readData.pReq->pcbActual != &u32Dummy)
556 {
557 RTPrintf(TEST_NAME ": format=%u, pReq->pv=%p, pReq->cb=%u, pReq->pcbActual=%p\n",
558 pBackend->readData.format, pBackend->readData.pReq->pv,
559 pBackend->readData.pReq->cb,
560 pBackend->readData.pReq->pcbActual);
561 ++cErrors;
562 }
563 else
564 {
565 ClipCompleteDataRequestFromX11(client.pCtx, VERR_NO_DATA,
566 pBackend->readData.pReq, NULL, 43);
567 if ( pBackend->completeRead.rc != VERR_NO_DATA
568 || pBackend->completeRead.cbActual != 43)
569 {
570 RTPrintf(TEST_NAME ": rc=%Rrc, cbActual=%u\n",
571 pBackend->completeRead.rc,
572 pBackend->completeRead.cbActual);
573 ++cErrors;
574 }
575 }
576 }
577 void *pv;
578 uint32_t cb;
579 pBackend->writeData.pv = (void *)"testing";
580 pBackend->writeData.cb = sizeof("testing");
581 pBackend->writeData.format = 1234;
582 pBackend->reportData.format = 4321; /* XX this should be handled! */
583 rc = ClipRequestDataForX11(client.pCtx, 23, &pv, &cb);
584 if ( rc != VINF_SUCCESS
585 || strcmp((const char *)pv, "testing") != 0
586 || cb != sizeof("testing"))
587 {
588 RTPrintf("rc=%Rrc, pv=%p, cb=%u\n", rc, pv, cb);
589 ++cErrors;
590 }
591 else
592 RTMemFree(pv);
593 pBackend->writeData.timeout = true;
594 rc = ClipRequestDataForX11(client.pCtx, 23, &pv, &cb);
595 if (rc != VERR_TIMEOUT)
596 {
597 RTPrintf("rc=%Rrc, expected VERR_TIMEOUT\n", rc);
598 ++cErrors;
599 }
600 pBackend->writeData.pv = NULL;
601 pBackend->writeData.cb = 0;
602 pBackend->writeData.timeout = false;
603 rc = ClipRequestDataForX11(client.pCtx, 23, &pv, &cb);
604 if (rc != VERR_NO_DATA)
605 {
606 RTPrintf("rc=%Rrc, expected VERR_NO_DATA\n", rc);
607 ++cErrors;
608 }
609 /* Data arriving after a timeout should *not* cause any segfaults or
610 * memory leaks. Check with Valgrind! */
611 vboxClipboardWriteData(&client, (void *)"tested", sizeof("tested"), 999);
612 vboxClipboardDisconnect(&client);
613 if (cErrors > 0)
614 RTPrintf(TEST_NAME ": errors: %u\n", cErrors);
615 return cErrors > 0 ? 1 : 0;
616}
617#endif /* TESTCASE */
618
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette