Print this page
    
    
      
        | Split | 
	Close | 
      
      | Expand all | 
      | Collapse all | 
    
    
          --- old/usr/src/cmd/dlmgmtd/dlmgmt_main.c
          +++ new/usr/src/cmd/dlmgmtd/dlmgmt_main.c
   1    1  /*
   2    2   * CDDL HEADER START
   3    3   *
   4    4   * The contents of this file are subject to the terms of the
   5    5   * Common Development and Distribution License (the "License").
   6    6   * You may not use this file except in compliance with the License.
   7    7   *
   8    8   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9    9   * or http://www.opensolaris.org/os/licensing.
  10   10   * See the License for the specific language governing permissions
  11   11   * and limitations under the License.
  12   12   *
  13   13   * When distributing Covered Code, include this CDDL HEADER in each
  14   14   * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15   15   * If applicable, add the following below this CDDL HEADER, with the
  16   16   * fields enclosed by brackets "[]" replaced with your own identifying
  17   17   * information: Portions Copyright [yyyy] [name of copyright owner]
  18   18   *
  19   19   * CDDL HEADER END
  20   20   */
  21   21  
  22   22  /*
  23   23   * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  24   24   * Use is subject to license terms.
  25   25   * Copyright 2014 Joyent, Inc.  All rights reserved.
  26   26   */
  27   27  
  28   28  /*
  29   29   * The dlmgmtd daemon is started by the datalink-management SMF service.
  30   30   * This daemon is used to manage <link name, linkid> mapping and the
  31   31   * persistent datalink configuration.
  32   32   *
  33   33   * Today, the <link name, linkid> mapping and the persistent configuration
  34   34   * of datalinks is kept in /etc/dladm/datalink.conf, and the daemon keeps
  35   35   * a copy of the datalinks in the memory (see dlmgmt_id_avl and
  36   36   * dlmgmt_name_avl). The active <link name, linkid> mapping is kept in
  37   37   * /etc/svc/volatile/dladm cache file, so that the mapping can be recovered
  38   38   * when dlmgmtd exits for some reason (e.g., when dlmgmtd is accidentally
  39   39   * killed).
  40   40   */
  41   41  
  42   42  #include <assert.h>
  43   43  #include <errno.h>
  44   44  #include <fcntl.h>
  45   45  #include <priv.h>
  46   46  #include <signal.h>
  47   47  #include <stdlib.h>
  48   48  #include <stdio.h>
  49   49  #include <strings.h>
  50   50  #include <syslog.h>
  51   51  #include <zone.h>
  52   52  #include <sys/dld.h>
  53   53  #include <sys/dld_ioc.h>
  54   54  #include <sys/param.h>
  55   55  #include <sys/stat.h>
  56   56  #include <unistd.h>
  57   57  #include <libdladm_impl.h>
  58   58  #include <libdlmgmt.h>
  59   59  #include "dlmgmt_impl.h"
  60   60  
  61   61  const char              *progname;
  62   62  boolean_t               debug;
  63   63  static int              pfds[2];
  64   64  /*
  65   65   * This file descriptor to DLMGMT_DOOR cannot be in the libdladm
  66   66   * handle because the door isn't created when the handle is created.
  67   67   */
  68   68  static int              dlmgmt_door_fd = -1;
  69   69  
  70   70  /*
  71   71   * This libdladm handle is global so that dlmgmt_upcall_linkprop_init() can
  72   72   * pass to libdladm.  The handle is opened with "ALL" privileges, before
  73   73   * privileges are dropped in dlmgmt_drop_privileges().  It is not able to open
  74   74   * DLMGMT_DOOR at that time as it hasn't been created yet.  This door in the
  75   75   * handle is opened in the first call to dladm_door_fd().
  76   76   */
  77   77  dladm_handle_t          dld_handle = NULL;
  78   78  
  79   79  static void             dlmgmtd_exit(int);
  80   80  static int              dlmgmt_init();
  81   81  static void             dlmgmt_fini();
  82   82  static int              dlmgmt_set_privileges();
  83   83  
  84   84  static int
  85   85  dlmgmt_set_doorfd(boolean_t start)
  86   86  {
  87   87          dld_ioc_door_t did;
  88   88          int err = 0;
  89   89  
  90   90          assert(dld_handle != NULL);
  91   91  
  92   92          did.did_start_door = start;
  93   93  
  94   94          if (ioctl(dladm_dld_fd(dld_handle), DLDIOC_DOORSERVER, &did) == -1)
  95   95                  err = errno;
  96   96  
  97   97          return (err);
  98   98  }
  99   99  
 100  100  static int
 101  101  dlmgmt_door_init(void)
 102  102  {
 103  103          int err = 0;
 104  104  
 105  105          if ((dlmgmt_door_fd = door_create(dlmgmt_handler, NULL,
 106  106              DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
 107  107                  err = errno;
 108  108                  dlmgmt_log(LOG_ERR, "door_create() failed: %s",
 109  109                      strerror(err));
 110  110                  return (err);
 111  111          }
 112  112          return (err);
 113  113  }
 114  114  
 115  115  static void
 116  116  dlmgmt_door_fini(void)
 117  117  {
 118  118          if (dlmgmt_door_fd == -1)
  
    | 
      ↓ open down ↓ | 
    118 lines elided | 
    
      ↑ open up ↑ | 
  
 119  119                  return;
 120  120  
 121  121          if (door_revoke(dlmgmt_door_fd) == -1) {
 122  122                  dlmgmt_log(LOG_WARNING, "door_revoke(%s) failed: %s",
 123  123                      DLMGMT_DOOR, strerror(errno));
 124  124          }
 125  125          (void) dlmgmt_set_doorfd(B_FALSE);
 126  126          dlmgmt_door_fd = -1;
 127  127  }
 128  128  
 129      -static int
      129 +int
 130  130  dlmgmt_door_attach(zoneid_t zoneid, char *rootdir)
 131  131  {
 132  132          int     fd;
 133  133          int     err = 0;
 134  134          char    doorpath[MAXPATHLEN];
 135  135          struct stat statbuf;
 136  136  
 137  137          /* Handle running in a non-native branded zone (i.e. has /native) */
 138  138          (void) snprintf(doorpath, sizeof (doorpath), "%s/native%s",
 139  139              rootdir, DLMGMT_TMPFS_DIR);
 140  140          if (stat(doorpath, &statbuf) == 0) {
 141  141                  (void) snprintf(doorpath, sizeof (doorpath), "%s/native%s",
 142  142                      rootdir, DLMGMT_DOOR);
 143  143          } else {
 144  144                  (void) snprintf(doorpath, sizeof (doorpath), "%s%s",
 145  145                      rootdir, DLMGMT_DOOR);
 146  146          }
 147  147  
 148  148          /*
 149  149           * Create the door file for dlmgmtd.
 150  150           */
 151  151          if ((fd = open(doorpath, O_CREAT|O_RDONLY, 0644)) == -1) {
 152  152                  err = errno;
 153  153                  dlmgmt_log(LOG_ERR, "open(%s) failed: %s", doorpath,
 154  154                      strerror(err));
 155  155                  return (err);
 156  156          }
 157  157          (void) close(fd);
 158  158          if (chown(doorpath, UID_DLADM, GID_NETADM) == -1)
 159  159                  return (errno);
 160  160  
 161  161          /*
 162  162           * fdetach first in case a previous daemon instance exited
 163  163           * ungracefully.
 164  164           */
 165  165          (void) fdetach(doorpath);
 166  166          if (fattach(dlmgmt_door_fd, doorpath) != 0) {
 167  167                  err = errno;
 168  168                  dlmgmt_log(LOG_ERR, "fattach(%s) failed: %s", doorpath,
 169  169                      strerror(err));
 170  170          } else if (zoneid == GLOBAL_ZONEID) {
 171  171                  if ((err = dlmgmt_set_doorfd(B_TRUE)) != 0) {
 172  172                          dlmgmt_log(LOG_ERR, "cannot set kernel doorfd: %s",
 173  173                              strerror(err));
 174  174                  }
 175  175          }
 176  176  
 177  177          return (err);
 178  178  }
 179  179  
 180  180  /*
 181  181   * Create the /etc/svc/volatile/dladm/ directory if it doesn't exist, load the
 182  182   * datalink.conf data for this zone, and create/attach the door rendezvous
 183  183   * file.
 184  184   */
 185  185  int
 186  186  dlmgmt_zone_init(zoneid_t zoneid)
 187  187  {
 188  188          char    rootdir[MAXPATHLEN], tmpfsdir[MAXPATHLEN];
 189  189          int     err;
 190  190          struct stat statbuf;
 191  191  
 192  192          if (zoneid == GLOBAL_ZONEID) {
 193  193                  rootdir[0] = '\0';
 194  194          } else if (zone_getattr(zoneid, ZONE_ATTR_ROOT, rootdir,
 195  195              sizeof (rootdir)) < 0) {
 196  196                  return (errno);
 197  197          }
 198  198  
 199  199          /*
 200  200           * Create the DLMGMT_TMPFS_DIR directory.
 201  201           */
 202  202          (void) snprintf(tmpfsdir, sizeof (tmpfsdir), "%s%s", rootdir,
 203  203              DLMGMT_TMPFS_DIR);
 204  204          if (stat(tmpfsdir, &statbuf) < 0) {
 205  205                  if (mkdir(tmpfsdir, (mode_t)0755) < 0) {
 206  206                          /*
 207  207                           * Handle running in a non-native branded zone
 208  208                           * (i.e. has /native)
 209  209                           */
 210  210                          (void) snprintf(tmpfsdir, sizeof (tmpfsdir),
 211  211                              "%s/native%s", rootdir, DLMGMT_TMPFS_DIR);
 212  212                          if (mkdir(tmpfsdir, (mode_t)0755) < 0)
 213  213                                  return (errno);
 214  214                  }
 215  215          } else if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
 216  216                  return (ENOTDIR);
 217  217          }
 218  218  
 219  219          if ((chmod(tmpfsdir, 0755) < 0) ||
 220  220              (chown(tmpfsdir, UID_DLADM, GID_NETADM) < 0)) {
 221  221                  return (EPERM);
 222  222          }
 223  223  
 224  224          if ((err = dlmgmt_db_init(zoneid, rootdir)) != 0)
  
    | 
      ↓ open down ↓ | 
    85 lines elided | 
    
      ↑ open up ↑ | 
  
 225  225                  return (err);
 226  226          return (dlmgmt_door_attach(zoneid, rootdir));
 227  227  }
 228  228  
 229  229  /*
 230  230   * Initialize each running zone.
 231  231   */
 232  232  static int
 233  233  dlmgmt_allzones_init(void)
 234  234  {
 235      -        int             i;
      235 +        int             err, i;
 236  236          zoneid_t        *zids = NULL;
 237  237          uint_t          nzids, nzids_saved;
 238  238  
 239  239          if (zone_list(NULL, &nzids) != 0)
 240  240                  return (errno);
 241  241  again:
 242  242          nzids *= 2;
 243  243          if ((zids = malloc(nzids * sizeof (zoneid_t))) == NULL)
 244  244                  return (errno);
 245  245          nzids_saved = nzids;
 246  246          if (zone_list(zids, &nzids) != 0) {
 247  247                  free(zids);
 248  248                  return (errno);
 249  249          }
 250  250          if (nzids > nzids_saved) {
 251  251                  free(zids);
 252  252                  goto again;
 253  253          }
 254  254  
 255  255          for (i = 0; i < nzids; i++) {
 256      -                int res;
 257      -                zone_status_t status;
 258      -
 259      -                /*
 260      -                 * Skip over zones that have gone away or are going down
 261      -                 * since we got the list.  Process all zones in the list,
 262      -                 * logging errors for any that failed.
 263      -                 */
 264      -                if (zone_getattr(zids[i], ZONE_ATTR_STATUS, &status,
 265      -                    sizeof (status)) < 0)
 266      -                        continue;
 267      -                switch (status) {
 268      -                        case ZONE_IS_SHUTTING_DOWN:
 269      -                        case ZONE_IS_EMPTY:
 270      -                        case ZONE_IS_DOWN:
 271      -                        case ZONE_IS_DYING:
 272      -                        case ZONE_IS_DEAD:
 273      -                                /* FALLTHRU */
 274      -                                continue;
 275      -                        default:
 276      -                                break;
 277      -                }
 278      -                if ((res = dlmgmt_zone_init(zids[i])) != 0) {
 279      -                        (void) fprintf(stderr, "zone (%ld) init error %s",
 280      -                            zids[i], strerror(res));
 281      -                        dlmgmt_log(LOG_ERR, "zone (%d) init error %s",
 282      -                            zids[i], strerror(res));
 283      -                }
      256 +                if ((err = dlmgmt_zone_init(zids[i])) != 0)
      257 +                        break;
 284  258          }
 285  259          free(zids);
 286      -        return (0);
      260 +        return (err);
 287  261  }
 288  262  
 289  263  static int
 290  264  dlmgmt_init(void)
 291  265  {
 292  266          int     err;
 293  267          char    *fmri, *c;
 294  268          char    filename[MAXPATHLEN];
 295  269  
 296  270          if (dladm_open(&dld_handle) != DLADM_STATUS_OK) {
 297  271                  dlmgmt_log(LOG_ERR, "dladm_open() failed");
 298  272                  return (EPERM);
 299  273          }
 300  274  
 301  275          if (signal(SIGTERM, dlmgmtd_exit) == SIG_ERR ||
 302  276              signal(SIGINT, dlmgmtd_exit) == SIG_ERR) {
 303  277                  err = errno;
 304  278                  dlmgmt_log(LOG_ERR, "signal() for SIGTERM/INT failed: %s",
 305  279                      strerror(err));
 306  280                  return (err);
 307  281          }
 308  282  
 309  283          (void) unlink(ZONE_LOCK);
 310  284  
 311  285          /*
 312  286           * First derive the name of the cache file from the FMRI name. This
 313  287           * cache name is used to keep active datalink configuration.
 314  288           */
 315  289          if (debug) {
 316  290                  (void) snprintf(cachefile, MAXPATHLEN, "%s/%s%s",
 317  291                      DLMGMT_TMPFS_DIR, progname, ".debug.cache");
 318  292          } else {
 319  293                  if ((fmri = getenv("SMF_FMRI")) == NULL) {
 320  294                          dlmgmt_log(LOG_ERR, "dlmgmtd is an smf(5) managed "
 321  295                              "service and should not be run from the command "
 322  296                              "line.");
 323  297                          return (EINVAL);
 324  298                  }
 325  299  
 326  300                  /*
 327  301                   * The FMRI name is in the form of
 328  302                   * svc:/service/service:instance.  We need to remove the
 329  303                   * prefix "svc:/" and replace '/' with '-'.  The cache file
 330  304                   * name is in the form of "service:instance.cache".
 331  305                   */
 332  306                  if ((c = strchr(fmri, '/')) != NULL)
 333  307                          c++;
 334  308                  else
 335  309                          c = fmri;
 336  310                  (void) snprintf(filename, MAXPATHLEN, "%s.cache", c);
 337  311                  c = filename;
 338  312                  while ((c = strchr(c, '/')) != NULL)
 339  313                          *c = '-';
 340  314  
 341  315                  (void) snprintf(cachefile, MAXPATHLEN, "%s/%s",
 342  316                      DLMGMT_TMPFS_DIR, filename);
 343  317          }
 344  318  
 345  319          dlmgmt_linktable_init();
 346  320          if ((err = dlmgmt_door_init()) != 0)
 347  321                  goto done;
 348  322  
 349  323          /*
 350  324           * Load datalink configuration and create dlmgmtd door files for all
 351  325           * currently running zones.
 352  326           */
 353  327          if ((err = dlmgmt_allzones_init()) != 0)
 354  328                  dlmgmt_door_fini();
 355  329  
 356  330  done:
 357  331          if (err != 0)
 358  332                  dlmgmt_linktable_fini();
 359  333          return (err);
 360  334  }
 361  335  
 362  336  static void
 363  337  dlmgmt_fini(void)
 364  338  {
 365  339          dlmgmt_door_fini();
 366  340          dlmgmt_linktable_fini();
 367  341          if (dld_handle != NULL) {
 368  342                  dladm_close(dld_handle);
 369  343                  dld_handle = NULL;
 370  344          }
 371  345  }
 372  346  
 373  347  /*
 374  348   * This is called by the child process to inform the parent process to
 375  349   * exit with the given return value.
 376  350   */
 377  351  static void
 378  352  dlmgmt_inform_parent_exit(int rv)
 379  353  {
 380  354          if (debug)
 381  355                  return;
 382  356  
 383  357          if (write(pfds[1], &rv, sizeof (int)) != sizeof (int)) {
 384  358                  dlmgmt_log(LOG_WARNING,
 385  359                      "dlmgmt_inform_parent_exit() failed: %s", strerror(errno));
 386  360                  (void) close(pfds[1]);
 387  361                  exit(EXIT_FAILURE);
 388  362          }
 389  363          (void) close(pfds[1]);
 390  364  }
 391  365  
 392  366  /*ARGSUSED*/
 393  367  static void
 394  368  dlmgmtd_exit(int signo)
 395  369  {
 396  370          (void) close(pfds[1]);
 397  371          dlmgmt_fini();
 398  372          exit(EXIT_FAILURE);
 399  373  }
 400  374  
 401  375  static void
 402  376  usage(void)
 403  377  {
 404  378          (void) fprintf(stderr, "Usage: %s [-d]\n", progname);
 405  379          exit(EXIT_FAILURE);
 406  380  }
 407  381  
 408  382  /*
 409  383   * Restrict privileges to only those needed.
 410  384   */
 411  385  int
 412  386  dlmgmt_drop_privileges(void)
 413  387  {
 414  388          priv_set_t      *pset;
 415  389          priv_ptype_t    ptype;
 416  390          zoneid_t        zoneid = getzoneid();
 417  391          int             err = 0;
 418  392  
 419  393          if ((pset = priv_allocset()) == NULL)
 420  394                  return (errno);
 421  395  
 422  396          /*
 423  397           * The global zone needs PRIV_PROC_FORK so that it can fork() when it
 424  398           * issues db ops in non-global zones, PRIV_SYS_CONFIG to post
 425  399           * sysevents, and PRIV_SYS_DL_CONFIG to initialize link properties in
 426  400           * dlmgmt_upcall_linkprop_init().
 427  401           *
 428  402           * We remove non-basic privileges from the permitted (and thus
 429  403           * effective) set.  When executing in a non-global zone, dlmgmtd
 430  404           * only needs to read and write to files that it already owns.
 431  405           */
 432  406          priv_basicset(pset);
 433  407          (void) priv_delset(pset, PRIV_PROC_EXEC);
 434  408          (void) priv_delset(pset, PRIV_PROC_INFO);
 435  409          (void) priv_delset(pset, PRIV_PROC_SESSION);
 436  410          (void) priv_delset(pset, PRIV_FILE_LINK_ANY);
 437  411          if (zoneid == GLOBAL_ZONEID) {
 438  412                  ptype = PRIV_EFFECTIVE;
 439  413                  if (priv_addset(pset, PRIV_SYS_CONFIG) == -1 ||
 440  414                      priv_addset(pset, PRIV_SYS_DL_CONFIG) == -1)
 441  415                          err = errno;
 442  416          } else {
 443  417                  (void) priv_delset(pset, PRIV_PROC_FORK);
 444  418                  ptype = PRIV_PERMITTED;
 445  419          }
 446  420          if (err == 0 && setppriv(PRIV_SET, ptype, pset) == -1)
 447  421                  err = errno;
 448  422  done:
 449  423          priv_freeset(pset);
 450  424          return (err);
 451  425  }
 452  426  
 453  427  int
 454  428  dlmgmt_elevate_privileges(void)
 455  429  {
 456  430          priv_set_t      *privset;
 457  431          int             err = 0;
 458  432  
 459  433          if ((privset = priv_str_to_set("zone", ",", NULL)) == NULL)
 460  434                  return (errno);
 461  435          if (setppriv(PRIV_SET, PRIV_EFFECTIVE, privset) == -1)
 462  436                  err = errno;
 463  437          priv_freeset(privset);
 464  438          return (err);
 465  439  }
 466  440  
 467  441  /*
 468  442   * Set the uid of this daemon to the "dladm" user and drop privileges to only
 469  443   * those needed.
 470  444   */
 471  445  static int
 472  446  dlmgmt_set_privileges(void)
 473  447  {
 474  448          int err;
 475  449  
 476  450          (void) setgroups(0, NULL);
 477  451          if (setegid(GID_NETADM) == -1 || seteuid(UID_DLADM) == -1)
 478  452                  err = errno;
 479  453          else
 480  454                  err = dlmgmt_drop_privileges();
 481  455  done:
 482  456          return (err);
 483  457  }
 484  458  
 485  459  /*
 486  460   * Keep the pfds fd open, close other fds.
 487  461   */
 488  462  /*ARGSUSED*/
 489  463  static int
 490  464  closefunc(void *arg, int fd)
 491  465  {
 492  466          if (fd != pfds[1])
 493  467                  (void) close(fd);
 494  468          return (0);
 495  469  }
 496  470  
 497  471  static boolean_t
 498  472  dlmgmt_daemonize(void)
 499  473  {
 500  474          pid_t pid;
 501  475          int rv;
 502  476  
 503  477          if (pipe(pfds) < 0) {
 504  478                  (void) fprintf(stderr, "%s: pipe() failed: %s\n",
 505  479                      progname, strerror(errno));
 506  480                  exit(EXIT_FAILURE);
 507  481          }
 508  482  
 509  483          if ((pid = fork()) == -1) {
 510  484                  (void) fprintf(stderr, "%s: fork() failed: %s\n",
 511  485                      progname, strerror(errno));
 512  486                  exit(EXIT_FAILURE);
 513  487          } else if (pid > 0) { /* Parent */
 514  488                  (void) close(pfds[1]);
 515  489  
 516  490                  /*
 517  491                   * Read the child process's return value from the pfds.
 518  492                   * If the child process exits unexpected, read() returns -1.
 519  493                   */
 520  494                  if (read(pfds[0], &rv, sizeof (int)) != sizeof (int)) {
 521  495                          (void) kill(pid, SIGKILL);
 522  496                          rv = EXIT_FAILURE;
 523  497                  }
 524  498  
 525  499                  (void) close(pfds[0]);
 526  500                  exit(rv);
 527  501          }
 528  502  
 529  503          /* Child */
 530  504          (void) close(pfds[0]);
 531  505          (void) setsid();
 532  506  
 533  507          /*
 534  508           * Close all files except pfds[1].
 535  509           */
 536  510          (void) fdwalk(closefunc, NULL);
 537  511          (void) chdir("/");
 538  512          openlog(progname, LOG_PID, LOG_DAEMON);
 539  513          return (B_TRUE);
 540  514  }
 541  515  
 542  516  int
 543  517  main(int argc, char *argv[])
 544  518  {
 545  519          int opt, err;
 546  520  
 547  521          progname = strrchr(argv[0], '/');
 548  522          if (progname != NULL)
 549  523                  progname++;
 550  524          else
 551  525                  progname = argv[0];
 552  526  
 553  527          /*
 554  528           * Process options.
 555  529           */
 556  530          while ((opt = getopt(argc, argv, "d")) != EOF) {
 557  531                  switch (opt) {
 558  532                  case 'd':
 559  533                          debug = B_TRUE;
 560  534                          break;
 561  535                  default:
 562  536                          usage();
 563  537                  }
 564  538          }
 565  539  
 566  540          if (!debug && !dlmgmt_daemonize())
 567  541                  return (EXIT_FAILURE);
 568  542  
 569  543          if ((err = dlmgmt_init()) != 0) {
 570  544                  dlmgmt_log(LOG_ERR, "unable to initialize daemon: %s",
 571  545                      strerror(err));
 572  546                  goto child_out;
 573  547          } else if ((err = dlmgmt_set_privileges()) != 0) {
 574  548                  dlmgmt_log(LOG_ERR, "unable to set daemon privileges: %s",
 575  549                      strerror(err));
 576  550                  dlmgmt_fini();
 577  551                  goto child_out;
 578  552          }
 579  553  
 580  554          /*
 581  555           * Inform the parent process that it can successfully exit.
 582  556           */
 583  557          dlmgmt_inform_parent_exit(EXIT_SUCCESS);
 584  558  
 585  559          for (;;)
 586  560                  (void) pause();
 587  561  
 588  562  child_out:
 589  563          /* return from main() forcibly exits an MT process */
 590  564          dlmgmt_inform_parent_exit(EXIT_FAILURE);
 591  565          return (EXIT_FAILURE);
 592  566  }
  
    | 
      ↓ open down ↓ | 
    296 lines elided | 
    
      ↑ open up ↑ | 
  
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX