1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /* Asynchronous X property getting hack */
4 
5 /*
6  * Copyright (C) 2002 Havoc Pennington
7  * Copyright (C) 1986, 1998  The Open Group
8  *
9  * Permission to use, copy, modify, distribute, and sell this software
10  * and its documentation for any purpose is hereby granted without
11  * fee, provided that the above copyright notice appear in all copies
12  * and that both that copyright notice and this permission notice
13  * appear in supporting documentation.
14  *
15  * The above copyright notice and this permission notice shall be
16  * included in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21  * NONINFRINGEMENT.  IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR
22  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  *
26  * Except as contained in this notice, the name of The Open Group shall not be
27  * used in advertising or otherwise to promote the sale, use or other dealings
28  * in this Software without prior written authorization from The Open Group.
29  */
30 #if HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 
34 #include <assert.h>
35 
36 #undef DEBUG_SPEW
37 #ifdef DEBUG_SPEW
38 #include <stdio.h>
39 #endif
40 
41 #include "async-getprop.h"
42 
43 #define NEED_REPLIES
44 #include <X11/Xlibint.h>
45 
46 #ifndef NULL
47 #define NULL ((void*)0)
48 #endif
49 
50 typedef struct _ListNode ListNode;
51 typedef struct _AgPerDisplayData AgPerDisplayData;
52 
53 struct _ListNode
54 {
55   ListNode *next;
56 };
57 
58 struct _AgGetPropertyTask
59 {
60   ListNode node;
61 
62   AgPerDisplayData *dd;
63   Window window;
64   Atom property;
65 
66   unsigned long request_seq;
67   int error;
68 
69   Atom actual_type;
70   int  actual_format;
71 
72   unsigned long  n_items;
73   unsigned long  bytes_after;
74   char          *data;
75 
76   Bool have_reply;
77 };
78 
79 struct _AgPerDisplayData
80 {
81   ListNode node;
82   _XAsyncHandler async;
83 
84   Display *display;
85   ListNode *pending_tasks;
86   ListNode *pending_tasks_tail;
87   ListNode *completed_tasks;
88   ListNode *completed_tasks_tail;
89   int n_tasks_pending;
90   int n_tasks_completed;
91 };
92 
93 static ListNode *display_datas = NULL;
94 static ListNode *display_datas_tail = NULL;
95 
96 static void
append_to_list(ListNode ** head,ListNode ** tail,ListNode * task)97 append_to_list (ListNode **head,
98                 ListNode **tail,
99                 ListNode  *task)
100 {
101   task->next = NULL;
102 
103   if (*tail == NULL)
104     {
105       assert (*head == NULL);
106       *head = task;
107       *tail = task;
108     }
109   else
110     {
111       (*tail)->next = task;
112       *tail = task;
113     }
114 }
115 
116 static void
remove_from_list(ListNode ** head,ListNode ** tail,ListNode * task)117 remove_from_list (ListNode **head,
118                   ListNode **tail,
119                   ListNode  *task)
120 {
121   ListNode *prev;
122   ListNode *node;
123 
124   prev = NULL;
125   node = *head;
126   while (node != NULL)
127     {
128       if (node == task)
129         {
130           if (prev)
131             prev->next = node->next;
132           else
133             *head = node->next;
134 
135           if (node == *tail)
136             *tail = prev;
137 
138           break;
139         }
140 
141       prev = node;
142       node = node->next;
143     }
144 
145   /* can't remove what's not there */
146   assert (node != NULL);
147 
148   node->next = NULL;
149 }
150 
151 static void
move_to_completed(AgPerDisplayData * dd,AgGetPropertyTask * task)152 move_to_completed (AgPerDisplayData  *dd,
153                    AgGetPropertyTask *task)
154 {
155   remove_from_list (&dd->pending_tasks,
156                     &dd->pending_tasks_tail,
157                     &task->node);
158 
159   append_to_list (&dd->completed_tasks,
160                   &dd->completed_tasks_tail,
161                   &task->node);
162 
163   dd->n_tasks_pending -= 1;
164   dd->n_tasks_completed += 1;
165 }
166 
167 static AgGetPropertyTask*
find_pending_by_request_sequence(AgPerDisplayData * dd,unsigned long request_seq)168 find_pending_by_request_sequence (AgPerDisplayData *dd,
169                                   unsigned long     request_seq)
170 {
171   ListNode *node;
172 
173   /* if the sequence is after our last pending task, we
174    * aren't going to find a match
175    */
176   {
177     AgGetPropertyTask *task = (AgGetPropertyTask*) dd->pending_tasks_tail;
178     if (task != NULL)
179       {
180         if (task->request_seq < request_seq)
181           return NULL;
182         else if (task->request_seq == request_seq)
183           return task; /* why not check this */
184       }
185   }
186 
187   /* Generally we should get replies in the order we sent
188    * requests, so we should usually be using the task
189    * at the head of the list, if we use any task at all.
190    * I'm not sure this is 100% guaranteed, if it is,
191    * it would be a big speedup.
192    */
193 
194   node = dd->pending_tasks;
195   while (node != NULL)
196     {
197       AgGetPropertyTask *task = (AgGetPropertyTask*) node;
198 
199       if (task->request_seq == request_seq)
200         return task;
201 
202       node = node->next;
203     }
204 
205   return NULL;
206 }
207 
208 static Bool
async_get_property_handler(Display * dpy,xReply * rep,char * buf,int len,XPointer data)209 async_get_property_handler (Display *dpy,
210                             xReply  *rep,
211                             char    *buf,
212                             int      len,
213                             XPointer data)
214 {
215   xGetPropertyReply  replbuf;
216   xGetPropertyReply *reply;
217   AgGetPropertyTask *task;
218   AgPerDisplayData *dd;
219   int bytes_read;
220 
221   dd = (AgPerDisplayData*) data;
222 
223 #if 0
224   printf ("%s: seeing request seq %ld buflen %d\n", __FUNCTION__,
225           dpy->last_request_read, len);
226 #endif
227 
228   task = find_pending_by_request_sequence (dd, dpy->last_request_read);
229 
230   if (task == NULL)
231     return False;
232 
233   assert (dpy->last_request_read == task->request_seq);
234 
235   task->have_reply = True;
236   move_to_completed (dd, task);
237 
238   /* read bytes so far */
239   bytes_read = SIZEOF (xReply);
240 
241   if (rep->generic.type == X_Error)
242     {
243       xError errbuf;
244 
245       task->error = rep->error.errorCode;
246 
247 #ifdef DEBUG_SPEW
248       printf ("%s: error code = %d (ignoring error, eating %d bytes, generic.length = %ld)\n",
249               __FUNCTION__, task->error, (SIZEOF (xError) - bytes_read),
250               rep->generic.length);
251 #endif
252 
253       /* We return True (meaning we consumed the reply)
254        * because otherwise it would invoke the X error handler,
255        * and an async API is useless if you have to synchronously
256        * trap X errors. Also GetProperty can always fail, pretty
257        * much, so trapping errors is always what you want.
258        *
259        * We have to eat all the error reply data here.
260        * (kind of a charade as we know sizeof(xError) == sizeof(xReply))
261        *
262        * Passing discard = True seems to break things; I don't understand
263        * why, because there should be no extra data in an error reply,
264        * right?
265        */
266       _XGetAsyncReply (dpy, (char *)&errbuf, rep, buf, len,
267                        (SIZEOF (xError) - bytes_read) >> 2, /* in 32-bit words */
268                        False); /* really seems like it should be True */
269 
270       return True;
271     }
272 
273 #ifdef DEBUG_SPEW
274   printf ("%s: already read %d bytes reading %d more for total of %d; generic.length = %ld\n",
275           __FUNCTION__, bytes_read, (SIZEOF (xGetPropertyReply) - bytes_read) >> 2,
276           SIZEOF (xGetPropertyReply), rep->generic.length);
277 #endif
278 
279   /* (kind of a silly as we know sizeof(xGetPropertyReply) == sizeof(xReply)) */
280   reply = (xGetPropertyReply *)
281     _XGetAsyncReply (dpy, (char *)&replbuf, rep, buf, len,
282                      (SIZEOF (xGetPropertyReply) - bytes_read) >> 2, /* in 32-bit words */
283                      False); /* False means expecting more data to follow,
284                               * don't eat the rest of the reply
285                               */
286 
287   bytes_read = SIZEOF (xGetPropertyReply);
288 
289 #ifdef DEBUG_SPEW
290   printf ("%s: have reply propertyType = %ld format = %d n_items = %ld\n",
291           __FUNCTION__, reply->propertyType, reply->format, reply->nItems);
292 #endif
293 
294   assert (task->data == NULL);
295 
296   /* This is all copied from XGetWindowProperty().  Not sure we should
297    * LockDisplay(). Not sure I'm passing the right args to
298    * XGetAsyncData(). Not sure about a lot of things.
299    */
300 
301   /* LockDisplay (dpy); */
302 
303   if (reply->propertyType != None)
304     {
305       long nbytes, netbytes;
306 
307       /* this alignment macro from orbit2 */
308 #define ALIGN_VALUE(this, boundary) \
309   (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
310 
311       switch (reply->format)
312         {
313           /*
314            * One extra byte is malloced than is needed to contain the property
315            * data, but this last byte is null terminated and convenient for
316            * returning string properties, so the client doesn't then have to
317            * recopy the string to make it null terminated.
318            */
319         case 8:
320           nbytes = reply->nItems;
321           /* there's padding to word boundary */
322           netbytes = ALIGN_VALUE (nbytes, 4);
323           if (nbytes + 1 > 0 &&
324               (task->data = (char *) Xmalloc ((unsigned)nbytes + 1)))
325             {
326 #ifdef DEBUG_SPEW
327               printf ("%s: already read %d bytes using %ld, more eating %ld more\n",
328                       __FUNCTION__, bytes_read, nbytes, netbytes);
329 #endif
330               /* _XReadPad (dpy, (char *) task->data, netbytes); */
331               _XGetAsyncData (dpy, task->data, buf, len,
332                               bytes_read, nbytes,
333                               netbytes);
334             }
335           break;
336 
337         case 16:
338           nbytes = reply->nItems * sizeof (short);
339           netbytes = reply->nItems << 1;
340           netbytes = ALIGN_VALUE (netbytes, 4); /* align to word boundary */
341           if (nbytes + 1 > 0 &&
342               (task->data = (char *) Xmalloc ((unsigned)nbytes + 1)))
343             {
344 #ifdef DEBUG_SPEW
345               printf ("%s: already read %d bytes using %ld more, eating %ld more\n",
346                       __FUNCTION__, bytes_read, nbytes, netbytes);
347 #endif
348               /* _XRead16Pad (dpy, (short *) task->data, netbytes); */
349               _XGetAsyncData (dpy, task->data, buf, len,
350                               bytes_read, nbytes, netbytes);
351             }
352           break;
353 
354         case 32:
355           /* NOTE buffer is in longs to match XGetWindowProperty() */
356           nbytes = reply->nItems * sizeof (long);
357           netbytes = reply->nItems << 2; /* wire size is always 32 bits though */
358           if (nbytes + 1 > 0 &&
359               (task->data = (char *) Xmalloc ((unsigned)nbytes + 1)))
360             {
361 #ifdef DEBUG_SPEW
362               printf ("%s: already read %d bytes using %ld more, eating %ld more\n",
363                       __FUNCTION__, bytes_read, nbytes, netbytes);
364 #endif
365 
366               /* We have to copy the XGetWindowProperty() crackrock
367                * and get format 32 as long even on 64-bit platforms.
368                */
369               if (sizeof (long) == 8)
370                 {
371                   char *netdata;
372                   char *lptr;
373                   char *end_lptr;
374 
375                   /* Store the 32-bit values in the end of the array */
376                   netdata = task->data + nbytes / 2;
377 
378                   _XGetAsyncData (dpy, netdata, buf, len,
379                                   bytes_read, netbytes,
380                                   netbytes);
381 
382                   /* Now move the 32-bit values to the front */
383 
384                   lptr = task->data;
385                   end_lptr = task->data + nbytes;
386                   while (lptr != end_lptr)
387                     {
388                       *(long*) lptr = *(CARD32*) netdata;
389                       lptr += sizeof (long);
390                       netdata += sizeof (CARD32);
391                     }
392                 }
393               else
394                 {
395                   /* Here the wire format matches our actual format */
396                   _XGetAsyncData (dpy, task->data, buf, len,
397                                   bytes_read, netbytes,
398                                   netbytes);
399                 }
400             }
401           break;
402 
403         default:
404           /*
405            * This part of the code should never be reached.  If it is,
406            * the server sent back a property with an invalid format.
407            * This is a BadImplementation error.
408            *
409            * However this async GetProperty API doesn't report errors
410            * via the standard X mechanism, so don't do anything about
411            * it, other than store it in task->error.
412            */
413           {
414 #if 0
415             xError error;
416 #endif
417 
418             task->error = BadImplementation;
419 
420 #if 0
421             error.sequenceNumber = task->request_seq;
422             error.type = X_Error;
423             error.majorCode = X_GetProperty;
424             error.minorCode = 0;
425             error.errorCode = BadImplementation;
426 
427             _XError (dpy, &error);
428 #endif
429           }
430 
431           nbytes = netbytes = 0L;
432           break;
433         }
434 
435       if (task->data == NULL)
436         {
437           task->error = BadAlloc;
438 
439 #ifdef DEBUG_SPEW
440           printf ("%s: already read %d bytes eating %ld\n",
441                   __FUNCTION__, bytes_read, netbytes);
442 #endif
443           /* _XEatData (dpy, (unsigned long) netbytes); */
444           _XGetAsyncData (dpy, NULL, buf, len,
445                           bytes_read, 0, netbytes);
446 
447           /* UnlockDisplay (dpy); */
448           return BadAlloc; /* not Success */
449         }
450 
451       (task->data)[nbytes] = '\0';
452     }
453 
454 #ifdef DEBUG_SPEW
455   printf ("%s: have data\n", __FUNCTION__);
456 #endif
457 
458   task->actual_type = reply->propertyType;
459   task->actual_format = reply->format;
460   task->n_items = reply->nItems;
461   task->bytes_after = reply->bytesAfter;
462 
463   /* UnlockDisplay (dpy); */
464 
465   return True;
466 }
467 
468 static AgPerDisplayData*
get_display_data(Display * display,Bool create)469 get_display_data (Display *display,
470                   Bool     create)
471 {
472   ListNode *node;
473   AgPerDisplayData *dd;
474 
475   node = display_datas;
476   while (node != NULL)
477     {
478       dd = (AgPerDisplayData*) node;
479 
480       if (dd->display == display)
481         return dd;
482 
483       node = node->next;
484     }
485 
486   if (!create)
487     return NULL;
488 
489   dd = Xcalloc (1, sizeof (AgPerDisplayData));
490   if (dd == NULL)
491     return NULL;
492 
493   dd->display = display;
494   dd->async.next = display->async_handlers;
495   dd->async.handler = async_get_property_handler;
496   dd->async.data = (XPointer) dd;
497   dd->display->async_handlers = &dd->async;
498 
499   append_to_list (&display_datas,
500                   &display_datas_tail,
501                   &dd->node);
502 
503   return dd;
504 }
505 
506 static void
maybe_free_display_data(AgPerDisplayData * dd)507 maybe_free_display_data (AgPerDisplayData *dd)
508 {
509   if (dd->pending_tasks == NULL &&
510       dd->completed_tasks == NULL)
511     {
512       DeqAsyncHandler (dd->display, &dd->async);
513       remove_from_list (&display_datas, &display_datas_tail,
514                         &dd->node);
515       XFree (dd);
516     }
517 }
518 
519 LOCAL_SYMBOL AgGetPropertyTask*
ag_task_create(Display * dpy,Window window,Atom property,long offset,long length,Bool delete,Atom req_type)520 ag_task_create (Display *dpy,
521                 Window   window,
522                 Atom     property,
523                 long     offset,
524                 long     length,
525                 Bool     delete,
526                 Atom     req_type)
527 {
528   AgGetPropertyTask *task;
529   xGetPropertyReq *req;
530   AgPerDisplayData *dd;
531 
532   /* Fire up our request */
533   LockDisplay (dpy);
534 
535   dd = get_display_data (dpy, True);
536   if (dd == NULL)
537     {
538       UnlockDisplay (dpy);
539       return NULL;
540     }
541 
542   GetReq (GetProperty, req);
543   req->window = window;
544   req->property = property;
545   req->type = req_type;
546   req->delete = delete;
547   req->longOffset = offset;
548   req->longLength = length;
549 
550   /* Queue up our async task */
551   task = Xcalloc (1, sizeof (AgGetPropertyTask));
552   if (task == NULL)
553     {
554       UnlockDisplay (dpy);
555       return NULL;
556     }
557 
558   task->dd = dd;
559   task->window = window;
560   task->property = property;
561   task->request_seq = dpy->request;
562 
563   append_to_list (&dd->pending_tasks,
564                   &dd->pending_tasks_tail,
565                   &task->node);
566   dd->n_tasks_pending += 1;
567 
568   UnlockDisplay (dpy);
569 
570   SyncHandle ();
571 
572   return task;
573 }
574 
575 static void
free_task(AgGetPropertyTask * task)576 free_task (AgGetPropertyTask *task)
577 {
578   remove_from_list (&task->dd->completed_tasks,
579                     &task->dd->completed_tasks_tail,
580                     &task->node);
581   task->dd->n_tasks_completed -= 1;
582   maybe_free_display_data (task->dd);
583   XFree (task);
584 }
585 
586 LOCAL_SYMBOL Status
ag_task_get_reply_and_free(AgGetPropertyTask * task,Atom * actual_type,int * actual_format,unsigned long * nitems,unsigned long * bytesafter,unsigned char ** prop)587 ag_task_get_reply_and_free (AgGetPropertyTask  *task,
588                             Atom               *actual_type,
589                             int                *actual_format,
590                             unsigned long      *nitems,
591                             unsigned long      *bytesafter,
592                             unsigned char     **prop)
593 {
594   Display *dpy;
595 
596   *prop = NULL;
597 
598   dpy = task->dd->display; /* Xlib macros require a variable named "dpy" */
599 
600   if (task->error != Success)
601     {
602       Status s = task->error;
603 
604       free_task (task);
605 
606       return s;
607     }
608 
609   if (!task->have_reply)
610     {
611       free_task (task);
612 
613       return BadAlloc; /* not Success */
614     }
615 
616   *actual_type = task->actual_type;
617   *actual_format = task->actual_format;
618   *nitems = task->n_items;
619   *bytesafter = task->bytes_after;
620 
621   *prop = (unsigned char*) task->data; /* pass out ownership of task->data */
622 
623   SyncHandle ();
624 
625   free_task (task);
626 
627   return Success;
628 }
629 
630 LOCAL_SYMBOL Bool
ag_task_have_reply(AgGetPropertyTask * task)631 ag_task_have_reply (AgGetPropertyTask *task)
632 {
633   return task->have_reply;
634 }
635 
636 LOCAL_SYMBOL Atom
ag_task_get_property(AgGetPropertyTask * task)637 ag_task_get_property (AgGetPropertyTask *task)
638 {
639   return task->property;
640 }
641 
642 LOCAL_SYMBOL Window
ag_task_get_window(AgGetPropertyTask * task)643 ag_task_get_window (AgGetPropertyTask *task)
644 {
645   return task->window;
646 }
647 
648 LOCAL_SYMBOL Display*
ag_task_get_display(AgGetPropertyTask * task)649 ag_task_get_display (AgGetPropertyTask *task)
650 {
651   return task->dd->display;
652 }
653 
654 LOCAL_SYMBOL AgGetPropertyTask*
ag_get_next_completed_task(Display * display)655 ag_get_next_completed_task (Display *display)
656 {
657   AgPerDisplayData *dd;
658 
659   dd = get_display_data (display, False);
660 
661   if (dd == NULL)
662     return NULL;
663 
664 #ifdef DEBUG_SPEW
665   printf ("%d pending %d completed\n",
666           dd->n_tasks_pending,
667           dd->n_tasks_completed);
668 #endif
669 
670   return (AgGetPropertyTask*) dd->completed_tasks;
671 }
672 
673 LOCAL_SYMBOL void*
ag_Xmalloc(unsigned long bytes)674 ag_Xmalloc (unsigned long bytes)
675 {
676   return (void*) Xmalloc (bytes);
677 }
678 
679 LOCAL_SYMBOL void*
ag_Xmalloc0(unsigned long bytes)680 ag_Xmalloc0 (unsigned long bytes)
681 {
682   return (void*) Xcalloc (bytes, 1);
683 }
684