1036947ae6de5992ec70f98a3248108fa52b393c
[platform/upstream/dbus.git] / bus / services.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* services.c  Service management
3  *
4  * Copyright (C) 2003  Red Hat, Inc.
5  * Copyright (C) 2003  CodeFactory AB
6  *
7  * Licensed under the Academic Free License version 2.1
8  * 
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  * 
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  */
24 #include <dbus/dbus-hash.h>
25 #include <dbus/dbus-list.h>
26 #include <dbus/dbus-mempool.h>
27
28 #include "driver.h"
29 #include "services.h"
30 #include "connection.h"
31 #include "utils.h"
32 #include "activation.h"
33 #include "policy.h"
34 #include "bus.h"
35 #include "selinux.h"
36
37 struct BusService
38 {
39   int refcount;
40
41   BusRegistry *registry;
42   char *name;
43   DBusList *owners;
44   
45   unsigned int prohibit_replacement : 1;
46 };
47
48 struct BusRegistry
49 {
50   int refcount;
51
52   BusContext *context;
53   
54   DBusHashTable *service_hash;
55   DBusMemPool   *service_pool;
56
57   DBusHashTable *service_sid_table;
58 };
59
60 BusRegistry*
61 bus_registry_new (BusContext *context)
62 {
63   BusRegistry *registry;
64
65   registry = dbus_new0 (BusRegistry, 1);
66   if (registry == NULL)
67     return NULL;
68
69   registry->refcount = 1;
70   registry->context = context;
71   
72   registry->service_hash = _dbus_hash_table_new (DBUS_HASH_STRING,
73                                                  NULL, NULL);
74   if (registry->service_hash == NULL)
75     goto failed;
76   
77   registry->service_pool = _dbus_mem_pool_new (sizeof (BusService),
78                                                TRUE);
79   if (registry->service_pool == NULL)
80     goto failed;
81
82   registry->service_sid_table = NULL;
83   
84   return registry;
85
86  failed:
87   bus_registry_unref (registry);
88   return NULL;
89 }
90
91 BusRegistry *
92 bus_registry_ref (BusRegistry *registry)
93 {
94   _dbus_assert (registry->refcount > 0);
95   registry->refcount += 1;
96
97   return registry;
98 }
99
100 void
101 bus_registry_unref  (BusRegistry *registry)
102 {
103   _dbus_assert (registry->refcount > 0);
104   registry->refcount -= 1;
105
106   if (registry->refcount == 0)
107     {
108       if (registry->service_hash)
109         _dbus_hash_table_unref (registry->service_hash);
110       if (registry->service_pool)
111         _dbus_mem_pool_free (registry->service_pool);
112       if (registry->service_sid_table)
113         _dbus_hash_table_unref (registry->service_sid_table);
114       
115       dbus_free (registry);
116     }
117 }
118
119 BusService*
120 bus_registry_lookup (BusRegistry      *registry,
121                      const DBusString *service_name)
122 {
123   BusService *service;
124
125   service = _dbus_hash_table_lookup_string (registry->service_hash,
126                                             _dbus_string_get_const_data (service_name));
127
128   return service;
129 }
130
131 BusService*
132 bus_registry_ensure (BusRegistry               *registry,
133                      const DBusString          *service_name,
134                      DBusConnection            *owner_if_created,
135                      BusTransaction            *transaction,
136                      DBusError                 *error)
137 {
138   BusService *service;
139
140   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
141   
142   _dbus_assert (owner_if_created != NULL);
143   _dbus_assert (transaction != NULL);
144
145   service = _dbus_hash_table_lookup_string (registry->service_hash,
146                                             _dbus_string_get_const_data (service_name));
147   if (service != NULL)
148     return service;
149   
150   service = _dbus_mem_pool_alloc (registry->service_pool);
151   if (service == NULL)
152     {
153       BUS_SET_OOM (error);
154       return NULL;
155     }
156
157   service->registry = registry;  
158   service->refcount = 1;
159   
160   if (!_dbus_string_copy_data (service_name, &service->name))
161     {
162       _dbus_mem_pool_dealloc (registry->service_pool, service);
163       BUS_SET_OOM (error);
164       return NULL;
165     }
166
167   if (!bus_driver_send_service_owner_changed (service->name, 
168                                               NULL,
169                                               bus_connection_get_name (owner_if_created),
170                                               transaction, error))
171     {
172       bus_service_unref (service);
173       return NULL;
174     }
175
176   if (!bus_activation_service_created (bus_context_get_activation (registry->context),
177                                        service->name, transaction, error))
178     {
179       bus_service_unref (service);
180       return NULL;
181     }
182   
183   if (!bus_service_add_owner (service, owner_if_created,
184                               transaction, error))
185     {
186       bus_service_unref (service);
187       return NULL;
188     }
189   
190   if (!_dbus_hash_table_insert_string (registry->service_hash,
191                                        service->name,
192                                        service))
193     {
194       /* The add_owner gets reverted on transaction cancel */
195       BUS_SET_OOM (error);
196       return NULL;
197     }
198   
199   return service;
200 }
201
202 void
203 bus_registry_foreach (BusRegistry               *registry,
204                       BusServiceForeachFunction  function,
205                       void                      *data)
206 {
207   DBusHashIter iter;
208   
209   _dbus_hash_iter_init (registry->service_hash, &iter);
210   while (_dbus_hash_iter_next (&iter))
211     {
212       BusService *service = _dbus_hash_iter_get_value (&iter);
213
214       (* function) (service, data);
215     }
216 }
217
218 dbus_bool_t
219 bus_registry_list_services (BusRegistry *registry,
220                             char      ***listp,
221                             int         *array_len)
222 {
223   int i, j, len;
224   char **retval;
225   DBusHashIter iter;
226    
227   len = _dbus_hash_table_get_n_entries (registry->service_hash);
228   retval = dbus_new (char *, len + 1);
229
230   if (retval == NULL)
231     return FALSE;
232
233   _dbus_hash_iter_init (registry->service_hash, &iter);
234   i = 0;
235   while (_dbus_hash_iter_next (&iter))
236     {
237       BusService *service = _dbus_hash_iter_get_value (&iter);
238
239       retval[i] = _dbus_strdup (service->name);
240       if (retval[i] == NULL)
241         goto error;
242
243       i++;
244     }
245
246   retval[i] = NULL;
247   
248   if (array_len)
249     *array_len = len;
250   
251   *listp = retval;
252   return TRUE;
253   
254  error:
255   for (j = 0; j < i; j++)
256     dbus_free (retval[i]);
257   dbus_free (retval);
258
259   return FALSE;
260 }
261
262 dbus_bool_t
263 bus_registry_acquire_service (BusRegistry      *registry,
264                               DBusConnection   *connection,
265                               const DBusString *service_name,
266                               dbus_uint32_t     flags,
267                               dbus_uint32_t    *result,
268                               BusTransaction   *transaction,
269                               DBusError        *error)
270 {
271   dbus_bool_t retval;
272   DBusConnection *old_owner;
273   DBusConnection *current_owner;
274   BusClientPolicy *policy;
275   BusService *service;
276   BusActivation  *activation;
277   BusSELinuxID *sid;
278   
279   retval = FALSE;
280
281   if (_dbus_string_get_length (service_name) == 0)
282     {
283       dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
284                       "Zero-length service name is not allowed");
285       
286       _dbus_verbose ("Attempt to acquire zero-length service name\n");
287       
288       goto out;
289     }
290   
291   if (_dbus_string_get_byte (service_name, 0) == ':')
292     {
293       /* Not allowed; only base services can start with ':' */
294       dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
295                       "Cannot acquire a service starting with ':' such as \"%s\"",
296                       _dbus_string_get_const_data (service_name));
297       
298       _dbus_verbose ("Attempt to acquire invalid base service name \"%s\"",
299                      _dbus_string_get_const_data (service_name));
300       
301       goto out;
302     }
303
304   policy = bus_connection_get_policy (connection);
305   _dbus_assert (policy != NULL);
306
307   /* Note that if sid is #NULL then the bus's own context gets used
308    * in bus_connection_selinux_allows_acquire_service()
309    */
310   sid = bus_selinux_id_table_lookup (registry->service_sid_table,
311                                      service_name);
312
313   if (!bus_selinux_allows_acquire_service (connection, sid,
314                                            _dbus_string_get_const_data (service_name)))
315     {
316       dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
317                       "Connection \"%s\" is not allowed to own the service \"%s\" due "
318                       "to SELinux policy",
319                       bus_connection_is_active (connection) ?
320                       bus_connection_get_name (connection) :
321                       "(inactive)",
322                       _dbus_string_get_const_data (service_name));
323       goto out;
324     }
325       
326   if (!bus_client_policy_check_can_own (policy, connection,
327                                         service_name))
328     {
329       dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
330                       "Connection \"%s\" is not allowed to own the service \"%s\" due "
331                       "to security policies in the configuration file",
332                       bus_connection_is_active (connection) ?
333                       bus_connection_get_name (connection) :
334                       "(inactive)",
335                       _dbus_string_get_const_data (service_name));
336       goto out;
337     }
338
339   if (bus_connection_get_n_services_owned (connection) >=
340       bus_context_get_max_services_per_connection (registry->context))
341     {
342       dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
343                       "Connection \"%s\" is not allowed to own more services "
344                       "(increase limits in configuration file if required)",
345                       bus_connection_is_active (connection) ?
346                       bus_connection_get_name (connection) :
347                       "(inactive)");
348       goto out;
349     }
350   
351   service = bus_registry_lookup (registry, service_name);
352
353   if (service != NULL)
354     old_owner = bus_service_get_primary_owner (service);
355   else
356     old_owner = NULL;
357       
358   if (service == NULL)
359     {
360       service = bus_registry_ensure (registry,
361                                      service_name, connection, transaction, error);
362       if (service == NULL)
363         goto out;
364     }
365
366   current_owner = bus_service_get_primary_owner (service);
367
368   if (old_owner == NULL)
369     {
370       _dbus_assert (current_owner == connection);
371
372       bus_service_set_prohibit_replacement (service,
373                                             (flags & DBUS_SERVICE_FLAG_PROHIBIT_REPLACEMENT));      
374                         
375       *result = DBUS_SERVICE_REPLY_PRIMARY_OWNER;      
376     }
377   else if (old_owner == connection)
378     *result = DBUS_SERVICE_REPLY_ALREADY_OWNER;
379   else if (!((flags & DBUS_SERVICE_FLAG_REPLACE_EXISTING)))
380     *result = DBUS_SERVICE_REPLY_SERVICE_EXISTS;
381   else if (bus_service_get_prohibit_replacement (service))
382     {
383       /* Queue the connection */
384       if (!bus_service_add_owner (service, connection,
385                                   transaction, error))
386         goto out;
387       
388       *result = DBUS_SERVICE_REPLY_IN_QUEUE;
389     }
390   else
391     {
392       /* Replace the current owner */
393
394       /* We enqueue the new owner and remove the first one because
395        * that will cause ServiceAcquired and ServiceLost messages to
396        * be sent.
397        */
398       
399       if (!bus_service_add_owner (service, connection,
400                                   transaction, error))
401         goto out;
402
403       if (!bus_service_remove_owner (service, old_owner,
404                                      transaction, error))
405         goto out;
406       
407       _dbus_assert (connection == bus_service_get_primary_owner (service));
408       *result = DBUS_SERVICE_REPLY_PRIMARY_OWNER;
409     }
410
411   activation = bus_context_get_activation (registry->context);
412   retval = bus_activation_send_pending_auto_activation_messages (activation,
413                                                                  service,
414                                                                  transaction,
415                                                                  error);
416   
417  out:
418   return retval;
419 }
420
421 dbus_bool_t
422 bus_registry_set_service_context_table (BusRegistry   *registry,
423                                         DBusHashTable *table)
424 {
425   DBusHashTable *new_table;
426   DBusHashIter iter;
427   
428   new_table = bus_selinux_id_table_new ();
429   if (!new_table)
430     return FALSE;
431
432   _dbus_hash_iter_init (table, &iter);
433   while (_dbus_hash_iter_next (&iter))
434     {
435       const char *service = _dbus_hash_iter_get_string_key (&iter);
436       const char *context = _dbus_hash_iter_get_value (&iter);
437
438       if (!bus_selinux_id_table_insert (new_table,
439                                         service,
440                                         context))
441         return FALSE;
442     }
443   
444   if (registry->service_sid_table)
445     _dbus_hash_table_unref (registry->service_sid_table);
446   registry->service_sid_table = new_table;
447   return TRUE;
448 }
449
450 static void
451 bus_service_unlink_owner (BusService      *service,
452                           DBusConnection  *owner)
453 {
454   _dbus_list_remove_last (&service->owners, owner);
455   bus_connection_remove_owned_service (owner, service);
456 }
457
458 static void
459 bus_service_unlink (BusService *service)
460 {
461   _dbus_assert (service->owners == NULL);
462
463   /* the service may not be in the hash, if
464    * the failure causing transaction cancel
465    * was in the right place, but that's OK
466    */
467   _dbus_hash_table_remove_string (service->registry->service_hash,
468                                   service->name);
469   
470   bus_service_unref (service);
471 }
472
473 static void
474 bus_service_relink (BusService           *service,
475                     DBusPreallocatedHash *preallocated)
476 {
477   _dbus_assert (service->owners == NULL);
478   _dbus_assert (preallocated != NULL);
479
480   _dbus_hash_table_insert_string_preallocated (service->registry->service_hash,
481                                                preallocated,
482                                                service->name,
483                                                service);
484   
485   bus_service_ref (service);
486 }
487
488 /**
489  * Data used to represent an ownership cancellation in
490  * a bus transaction.
491  */
492 typedef struct
493 {
494   DBusConnection *connection; /**< the connection */
495   BusService *service;        /**< service to cancel ownership of */
496 } OwnershipCancelData;
497
498 static void
499 cancel_ownership (void *data)
500 {
501   OwnershipCancelData *d = data;
502
503   /* We don't need to send messages notifying of these
504    * changes, since we're reverting something that was
505    * cancelled (effectively never really happened)
506    */
507   bus_service_unlink_owner (d->service, d->connection);
508   
509   if (d->service->owners == NULL)
510     bus_service_unlink (d->service);
511 }
512
513 static void
514 free_ownership_cancel_data (void *data)
515 {
516   OwnershipCancelData *d = data;
517
518   dbus_connection_unref (d->connection);
519   bus_service_unref (d->service);
520   
521   dbus_free (d);
522 }
523
524 static dbus_bool_t
525 add_cancel_ownership_to_transaction (BusTransaction *transaction,
526                                      BusService     *service,
527                                      DBusConnection *connection)
528 {
529   OwnershipCancelData *d;
530
531   d = dbus_new (OwnershipCancelData, 1);
532   if (d == NULL)
533     return FALSE;
534   
535   d->service = service;
536   d->connection = connection;
537
538   if (!bus_transaction_add_cancel_hook (transaction, cancel_ownership, d,
539                                         free_ownership_cancel_data))
540     {
541       dbus_free (d);
542       return FALSE;
543     }
544
545   bus_service_ref (d->service);
546   dbus_connection_ref (d->connection);
547   
548   return TRUE;
549 }
550
551 /* this function is self-cancelling if you cancel the transaction */
552 dbus_bool_t
553 bus_service_add_owner (BusService     *service,
554                        DBusConnection *owner,
555                        BusTransaction *transaction,
556                        DBusError      *error)
557 {
558   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
559   
560  /* Send service acquired message first, OOM will result
561   * in cancelling the transaction
562   */
563   if (service->owners == NULL)
564     {
565       if (!bus_driver_send_service_acquired (owner, service->name, transaction, error))
566         return FALSE;
567     }
568   
569   if (!_dbus_list_append (&service->owners,
570                           owner))
571     {
572       BUS_SET_OOM (error);
573       return FALSE;
574     }
575
576   if (!bus_connection_add_owned_service (owner, service))
577     {
578       _dbus_list_remove_last (&service->owners, owner);
579       BUS_SET_OOM (error);
580       return FALSE;
581     }
582
583   if (!add_cancel_ownership_to_transaction (transaction,
584                                             service,
585                                             owner))
586     {
587       bus_service_unlink_owner (service, owner);
588       BUS_SET_OOM (error);
589       return FALSE;
590     }
591   
592   return TRUE;
593 }
594
595 typedef struct
596 {
597   DBusConnection *connection;
598   BusService     *service;
599   DBusConnection *before_connection; /* restore to position before this connection in owners list */
600   DBusList       *connection_link;
601   DBusList       *service_link;
602   DBusPreallocatedHash *hash_entry;
603 } OwnershipRestoreData;
604
605 static void
606 restore_ownership (void *data)
607 {
608   OwnershipRestoreData *d = data;
609   DBusList *link;
610
611   _dbus_assert (d->service_link != NULL);
612   _dbus_assert (d->connection_link != NULL);
613   
614   if (d->service->owners == NULL)
615     {
616       _dbus_assert (d->hash_entry != NULL);
617       bus_service_relink (d->service, d->hash_entry);
618     }
619   else
620     {
621       _dbus_assert (d->hash_entry == NULL);
622     }
623   
624   /* We don't need to send messages notifying of these
625    * changes, since we're reverting something that was
626    * cancelled (effectively never really happened)
627    */
628   link = _dbus_list_get_first_link (&d->service->owners);
629   while (link != NULL)
630     {
631       if (link->data == d->before_connection)
632         break;
633
634       link = _dbus_list_get_next_link (&d->service->owners, link);
635     }
636   
637   _dbus_list_insert_before_link (&d->service->owners, link, d->connection_link);
638
639   /* Note that removing then restoring this changes the order in which
640    * ServiceDeleted messages are sent on destruction of the
641    * connection.  This should be OK as the only guarantee there is
642    * that the base service is destroyed last, and we never even
643    * tentatively remove the base service.
644    */
645   bus_connection_add_owned_service_link (d->connection, d->service_link);
646   
647   d->hash_entry = NULL;
648   d->service_link = NULL;
649   d->connection_link = NULL;
650 }
651
652 static void
653 free_ownership_restore_data (void *data)
654 {
655   OwnershipRestoreData *d = data;
656
657   if (d->service_link)
658     _dbus_list_free_link (d->service_link);
659   if (d->connection_link)
660     _dbus_list_free_link (d->connection_link);
661   if (d->hash_entry)
662     _dbus_hash_table_free_preallocated_entry (d->service->registry->service_hash,
663                                               d->hash_entry);
664
665   dbus_connection_unref (d->connection);
666   bus_service_unref (d->service);
667   
668   dbus_free (d);
669 }
670
671 static dbus_bool_t
672 add_restore_ownership_to_transaction (BusTransaction *transaction,
673                                       BusService     *service,
674                                       DBusConnection *connection)
675 {
676   OwnershipRestoreData *d;
677   DBusList *link;
678
679   d = dbus_new (OwnershipRestoreData, 1);
680   if (d == NULL)
681     return FALSE;
682   
683   d->service = service;
684   d->connection = connection;
685   d->service_link = _dbus_list_alloc_link (service);
686   d->connection_link = _dbus_list_alloc_link (connection);
687   d->hash_entry = _dbus_hash_table_preallocate_entry (service->registry->service_hash);
688   
689   bus_service_ref (d->service);
690   dbus_connection_ref (d->connection);
691
692   d->before_connection = NULL;
693   link = _dbus_list_get_first_link (&service->owners);
694   while (link != NULL)
695     {
696       if (link->data == connection)
697         {
698           link = _dbus_list_get_next_link (&service->owners, link);
699
700           if (link)
701             d->before_connection = link->data;
702
703           break;
704         }
705       
706       link = _dbus_list_get_next_link (&service->owners, link);
707     }
708   
709   if (d->service_link == NULL ||
710       d->connection_link == NULL ||
711       d->hash_entry == NULL ||
712       !bus_transaction_add_cancel_hook (transaction, restore_ownership, d,
713                                         free_ownership_restore_data))
714     {
715       free_ownership_restore_data (d);
716       return FALSE;
717     }
718   
719   return TRUE;
720 }
721
722 /* this function is self-cancelling if you cancel the transaction */
723 dbus_bool_t
724 bus_service_remove_owner (BusService     *service,
725                           DBusConnection *owner,
726                           BusTransaction *transaction,
727                           DBusError      *error)
728 {
729   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
730   
731   /* We send out notifications before we do any work we
732    * might have to undo if the notification-sending failed
733    */
734   
735   /* Send service lost message */
736   if (bus_service_get_primary_owner (service) == owner)
737     {
738       if (!bus_driver_send_service_lost (owner, service->name,
739                                          transaction, error))
740         return FALSE;
741     }
742
743   if (service->owners == NULL)
744     {
745       _dbus_assert_not_reached ("Tried to remove owner of a service that has no owners");
746     }
747   else if (_dbus_list_length_is_one (&service->owners))
748     {
749       if (!bus_driver_send_service_owner_changed (service->name,
750                                                   bus_connection_get_name (owner),
751                                                   NULL,
752                                                   transaction, error))
753         return FALSE;
754     }
755   else
756     {
757       DBusList *link;
758       DBusConnection *new_owner;
759       link = _dbus_list_get_first_link (&service->owners);
760       _dbus_assert (link != NULL);
761       link = _dbus_list_get_next_link (&service->owners, link);
762       _dbus_assert (link != NULL);
763
764       new_owner = link->data;
765
766       if (!bus_driver_send_service_owner_changed (service->name,
767                                                   bus_connection_get_name (owner),
768                                                   bus_connection_get_name (new_owner),
769                                                   transaction, error))
770         return FALSE;
771
772       /* This will be our new owner */
773       if (!bus_driver_send_service_acquired (new_owner,
774                                              service->name,
775                                              transaction,
776                                              error))
777         return FALSE;
778     }
779
780   if (!add_restore_ownership_to_transaction (transaction, service, owner))
781     {
782       BUS_SET_OOM (error);
783       return FALSE;
784     }
785   
786   bus_service_unlink_owner (service, owner);
787
788   if (service->owners == NULL)
789     bus_service_unlink (service);
790
791   return TRUE;
792 }
793
794 BusService *
795 bus_service_ref (BusService *service)
796 {
797   _dbus_assert (service->refcount > 0);
798   
799   service->refcount += 1;
800
801   return service;
802 }
803
804 void
805 bus_service_unref (BusService *service)
806 {
807   _dbus_assert (service->refcount > 0);
808   
809   service->refcount -= 1;
810
811   if (service->refcount == 0)
812     {
813       _dbus_assert (service->owners == NULL);
814       
815       dbus_free (service->name);
816       _dbus_mem_pool_dealloc (service->registry->service_pool, service);
817     }
818 }
819
820 DBusConnection*
821 bus_service_get_primary_owner (BusService *service)
822 {
823   return _dbus_list_get_first (&service->owners);
824 }
825
826 const char*
827 bus_service_get_name (BusService *service)
828 {
829   return service->name;
830 }
831
832 void
833 bus_service_set_prohibit_replacement (BusService  *service,
834                                       dbus_bool_t  prohibit_replacement)
835 {
836   service->prohibit_replacement = prohibit_replacement != FALSE;
837 }
838
839 dbus_bool_t
840 bus_service_get_prohibit_replacement (BusService *service)
841 {
842   return service->prohibit_replacement;
843 }
844
845 dbus_bool_t
846 bus_service_has_owner (BusService     *service,
847                        DBusConnection *owner)
848 {
849   DBusList *link;
850
851   link = _dbus_list_get_first_link (&service->owners);
852   
853   while (link != NULL)
854     {
855       if (link->data == owner)
856         return TRUE;
857       
858       link = _dbus_list_get_next_link (&service->owners, link);
859     }
860
861   return FALSE;
862 }