Print this page
Merge cleanup from previous six commits
OS-2564 zone boot failed: could not start zoneadmd
OS-4166 zlogin to zfd needs TIOCSWINSZ support
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
OS-3075 zone long boot args aren't passed through
        
@@ -20,11 +20,11 @@
  */
 
 /*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
- * Copyright 2012 Joyent, Inc.  All rights reserved.
+ * Copyright 2015 Joyent, Inc.
  * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
  */
 
 /*
  * Console support for zones requires a significant infrastructure.  The
@@ -116,24 +116,28 @@
 #define ZCONSNEX_DEVTREEPATH    "/pseudo/zconsnex@1"
 #define ZCONSNEX_FILEPATH       "/devices/pseudo/zconsnex@1"
 
 #define CONSOLE_SOCKPATH        ZONES_TMPDIR "/%s.console_sock"
 
+#define ZCONS_RETRY             10
+
 static int      serverfd = -1;  /* console server unix domain socket fd */
 char boot_args[BOOTARGS_MAX];
-char bad_boot_arg[BOOTARGS_MAX];
 
 /*
  * The eventstream is a simple one-directional flow of messages from the
  * door server to the console subsystem, implemented with a pipe.
  * It is used to wake up the console poller when it needs to take action,
  * message the user, die off, etc.
  */
 static int eventstream[2];
 
+/* flag used to cope with race creating master zcons devlink */
+static boolean_t master_zcons_failed = B_FALSE;
+/* flag to track if we've seen a state change when there is no master zcons */
+static boolean_t state_changed = B_FALSE;
 
-
 int
 eventstream_init()
 {
         if (pipe(eventstream) == -1)
                 return (-1);
@@ -406,46 +410,59 @@
 
         /*
          * Open the master side of the console and issue the ZC_HOLDSLAVE ioctl,
          * which will cause the master to retain a reference to the slave.
          * This prevents ttymon from blowing through the slave's STREAMS anchor.
+         *
+         * In very rare cases the open returns ENOENT if devfs doesn't have
+         * everything setup yet due to heavy zone startup load. Wait for
+         * 1 sec. and retry a few times. Even if we can't setup the zone's
+         * console, we still go ahead and boot the zone.
          */
         (void) snprintf(conspath, sizeof (conspath), "/dev/zcons/%s/%s",
             zone_name, ZCONS_MASTER_NAME);
         if ((masterfd = open(conspath, O_RDWR | O_NOCTTY)) == -1) {
                 zerror(zlogp, B_TRUE, "ERROR: could not open master side of "
                     "zone console for %s to acquire slave handle", zone_name);
-                goto error;
+                master_zcons_failed = B_TRUE;
         }
         (void) snprintf(conspath, sizeof (conspath), "/dev/zcons/%s/%s",
             zone_name, ZCONS_SLAVE_NAME);
-        if ((slavefd = open(conspath, O_RDWR | O_NOCTTY)) == -1) {
+        for (i = 0; i < ZCONS_RETRY; i++) {
+                slavefd = open(conspath, O_RDWR | O_NOCTTY);
+                if (slavefd >= 0 || errno != ENOENT)
+                        break;
+                (void) sleep(1);
+        }
+        if (slavefd == -1)
                 zerror(zlogp, B_TRUE, "ERROR: could not open slave side of zone"
                     " console for %s to acquire slave handle", zone_name);
-                (void) close(masterfd);
-                goto error;
-        }
+
         /*
          * This ioctl can occasionally return ENXIO if devfs doesn't have
          * everything plumbed up yet due to heavy zone startup load. Wait for
          * 1 sec. and retry a few times before we fail to boot the zone.
          */
-        for (i = 0; i < 5; i++) {
-                if (ioctl(masterfd, ZC_HOLDSLAVE, (caddr_t)(intptr_t)slavefd)
-                    == 0) {
+        if (masterfd != -1 && slavefd != -1) {
+                for (i = 0; i < ZCONS_RETRY; i++) {
+                        if (ioctl(masterfd, ZC_HOLDSLAVE,
+                            (caddr_t)(intptr_t)slavefd) == 0) {
                         rv = 0;
                         break;
                 } else if (errno != ENXIO) {
                         break;
                 }
                 (void) sleep(1);
         }
         if (rv != 0)
-                zerror(zlogp, B_TRUE, "ERROR: error while acquiring slave "
-                    "handle of zone console for %s", zone_name);
+                        zerror(zlogp, B_TRUE, "ERROR: error while acquiring "
+                            "slave handle of zone console for %s", zone_name);
+        }
 
+        if (slavefd != -1)
         (void) close(slavefd);
+        if (masterfd != -1)
         (void) close(masterfd);
 
 error:
         if (ddef_hdl)
                 devctl_ddef_free(ddef_hdl);
@@ -515,10 +532,11 @@
 {
         char buf[BUFSIZ], *bufp;
         size_t buflen = sizeof (buf);
         char c = '\0';
         int i = 0, r;
+        ucred_t *cred = NULL;
 
         /* "eat up the ident string" case, for simplicity */
         if (pid == NULL) {
                 assert(locale == NULL && locale_len == 0);
                 while (read(clifd, &c, 1) == 1) {
@@ -548,22 +566,26 @@
                 while ((r = read(clifd, &c, sizeof (c))) > 0)
                         if (c == '\n')
                                 break;
         }
 
+        if (getpeerucred(clifd, &cred) == 0) {
+                *pid = ucred_getpid((const ucred_t *)cred);
+                ucred_free(cred);
+        } else {
+                return (-1);
+        }
+
         /*
          * Parse buffer for message of the form:
-         * IDENT <pid> <locale> <disconnect flag>
+         * IDENT <locale> <disconnect flag>
          */
         bufp = buf;
         if (strncmp(bufp, "IDENT ", 6) != 0)
                 return (-1);
         bufp += 6;
         errno = 0;
-        *pid = strtoll(bufp, &bufp, 10);
-        if (errno != 0)
-                return (-1);
 
         while (*bufp != '\0' && isspace(*bufp))
                 bufp++;
         buflen = strlen(bufp) - 1;
         *disconnect = atoi(&bufp[buflen]);
@@ -665,18 +687,10 @@
                 if (dflag)
                         str = "NOTICE: Zone boot failed.  Disconnecting...";
                 else
                         str = "NOTICE: Zone boot failed";
                 break;
-        case Z_EVT_ZONE_BADARGS:
-                /*LINTED*/
-                (void) snprintf(lmsg, sizeof (lmsg),
-                    localize_msg(clilocale,
-                    "WARNING: Ignoring invalid boot arguments: %s"),
-                    bad_boot_arg);
-                lstr = lmsg;
-                break;
         default:
                 return;
         }
 
         if (lstr == NULL)
@@ -876,11 +890,10 @@
 init_console(zlog_t *zlogp)
 {
         if (init_console_dev(zlogp) == -1) {
                 zerror(zlogp, B_FALSE,
                     "console setup: device initialization failed");
-                return (-1);
         }
 
         if ((serverfd = init_console_sock(zlogp)) == -1) {
                 zerror(zlogp, B_FALSE,
                     "console setup: socket initialization failed");
@@ -888,10 +901,21 @@
         }
         return (0);
 }
 
 /*
+ * Maintain a simple flag that tracks if we have seen at least one state
+ * change. This is currently only used to handle the special case where we are
+ * running without a console device, which is what normally drives shutdown.
+ */
+void
+zcons_statechanged()
+{
+        state_changed = B_TRUE;
+}
+
+/*
  * serve_console() is the master loop for driving console I/O.  It is also the
  * routine which is ultimately responsible for "pulling the plug" on zoneadmd
  * when it realizes that the daemon should shut down.
  *
  * The rules for shutdown are: there must be no console client, and the zone
@@ -905,17 +929,58 @@
 serve_console(zlog_t *zlogp)
 {
         int masterfd;
         zone_state_t zstate;
         char conspath[MAXPATHLEN];
+        static boolean_t cons_warned = B_FALSE;
 
         (void) snprintf(conspath, sizeof (conspath),
             "/dev/zcons/%s/%s", zone_name, ZCONS_MASTER_NAME);
 
         for (;;) {
                 masterfd = open(conspath, O_RDWR|O_NONBLOCK|O_NOCTTY);
                 if (masterfd == -1) {
+                        if (master_zcons_failed) {
+                                /*
+                                 * If we don't have a console and the zone is
+                                 * not shutting down, there may have been a
+                                 * race/failure with devfs while creating the
+                                 * console. In this case we want to leave the
+                                 * zone up, even without a console, so
+                                 * periodically recheck.
+                                 */
+                                int i;
+
+                                /*
+                                 * In the normal flow of this loop, we use
+                                 * do_console_io to give things a chance to get
+                                 * going first. However, in this case we can't
+                                 * use that, so we have to wait for at least
+                                 * one state change before checking the state.
+                                 */
+                                for (i = 0; i < 60; i++) {
+                                        if (state_changed)
+                                                break;
+                                        (void) sleep(1);
+                                }
+
+                                if (i < 60 && zone_get_state(zone_name,
+                                    &zstate) == Z_OK &&
+                                    (zstate == ZONE_STATE_READY ||
+                                    zstate == ZONE_STATE_RUNNING)) {
+                                        if (!cons_warned) {
+                                                zerror(zlogp, B_FALSE,
+                                                    "WARNING: missing zone "
+                                                    "console for %s",
+                                                    zone_name);
+                                                cons_warned = B_TRUE;
+                                        }
+                                        (void) sleep(ZCONS_RETRY);
+                                        continue;
+                                }
+                        }
+
                         zerror(zlogp, B_TRUE, "failed to open console master");
                         (void) mutex_lock(&lock);
                         goto death;
                 }