VirtualBox

source: vbox/trunk/src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp@ 78157

Last change on this file since 78157 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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