root/usr/src/cmd/tzreload/tzreload.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*      All Rights Reserved     */

#include <atomic.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <locale.h>
#include <libintl.h>
#include <zone.h>
#include <libzonecfg.h>
#include <sys/brand.h>
#include <dlfcn.h>

#define TZSYNC_FILE     "/var/run/tzsync"

static void     init_file(void);
static void     doit(const char *zname, const char *zroot, int get);
static void     counter_get(const char *zname, int fd);
static void     counter_set(int fd);
static void     walk_zones(int get);
static void     send_cron_msg(const char *zname, const char *zroot);

/*
 * There are undocumeted command line options:
 * -l   list the value of semaphore.
 * -I   initialize the semaphore file (ie /var/run/tzsync)
 */

int
main(int argc, char **argv)
{
        int     arg;
        int     all = 0, get = 0, init = 0;

        (void) setlocale(LC_ALL, "");
        (void) textdomain(TEXT_DOMAIN);

        while ((arg = getopt(argc, argv, "alI")) != EOF) {
                switch (arg) {
                case 'a':
                        all = 1;
                        break;
                case 'l':
                        get = 1;
                        break;
                case 'I':
                        init = 1;
                        break;
                default:
                        (void) fprintf(stderr,
                            gettext("Usage: tzreload [-a]\n"));
                        exit(1);
                }
        }

        if (init) {
                init_file();
                return (0);
        }

        if (all)
                walk_zones(get);
        else
                doit(NULL, "", get);

        return (0);
}

/*
 * Create /var/run/tzsync atomically.
 *
 * While creating the /var/run/tzsync initially, there is a timing window
 * that the file is created but no disk block is allocated (empty file).
 * If apps mmap'ed the file at the very moment, it succeeds but accessing
 * the memory page causes a segfault since disk block isn't yet allocated.
 * To avoid this situation, we create a temp file which has pagesize block
 * assigned, and then rename it to tzsync.
 */
static void
init_file(void)
{
        char    path[sizeof (TZSYNC_FILE) + 16];
        char    *buf;
        int     fd, pgsz;
        struct stat st;

        /* We don't allow to re-create the file */
        if (stat(TZSYNC_FILE, &st) == 0) {
                (void) fprintf(stderr, gettext("%s already exists.\n"),
                    TZSYNC_FILE);
                exit(1);
        }

        pgsz = sysconf(_SC_PAGESIZE);

        (void) strcpy(path, TZSYNC_FILE "XXXXXX");
        if ((fd = mkstemp(path)) == -1) {
                (void) fprintf(stderr,
                    gettext("failed to create a temporary file.\n"));
                exit(1);
        }

        if ((buf = calloc(1, pgsz)) == NULL) {
                (void) fprintf(stderr, gettext("Insufficient memory.\n"));
errout:
                (void) close(fd);
                (void) unlink(path);
                exit(1);
        }

        if (write(fd, buf, pgsz) != pgsz) {
                (void) fprintf(stderr,
                    gettext("failed to create tzsync file, %s\n"),
                    strerror(errno));
                goto errout;
        }
        (void) close(fd);

        /* link it */
        if (link(path, TZSYNC_FILE) != 0) {
                if (errno == EEXIST) {
                        (void) fprintf(stderr, gettext("%s already exists.\n"),
                            TZSYNC_FILE);
                } else {
                        (void) fprintf(stderr, gettext("failed to create %s\n"),
                            TZSYNC_FILE);
                }
                (void) unlink(path);
                exit(1);
        }
        (void) unlink(path);

        /*
         * Unplivileged apps may fail to open the file until the chmod
         * below succeeds. However, it's okay as long as open() fails;
         * ctime() won't cache zoneinfo until file is opened and mmap'd.
         */

        /* /var/run/tzsync has been made. Adjust permission */
        if (chmod(TZSYNC_FILE, 0644) != 0) {
                (void) fprintf(stderr,
                    gettext("failed to change permission of %s\n"),
                    TZSYNC_FILE);
                (void) unlink(TZSYNC_FILE);
                exit(1);
        }
}

/*
 * Open the /var/run/tzsync, then set or get the semaphore.
 *
 * zname        name of zone (NULL if no need to consider zones)
 * zroot        zone's root path
 * get          get/set semaphore
 */
static void
doit(const char *zname, const char *zroot, int get)
{
        int     fd;
        char    file[PATH_MAX + 1];

        if (strlcpy(file, zroot, sizeof (file)) >= sizeof (file) ||
            strlcat(file, TZSYNC_FILE, sizeof (file)) >= sizeof (file)) {
                (void) fprintf(stderr, gettext("zonepath too long\n"));
                exit(1);
        }

        if ((fd = open(file, get ? O_RDONLY : O_RDWR)) < 0) {
                (void) fprintf(stderr,
                    gettext("Can't open file %s, %s\n"),
                    file, strerror(errno));
                exit(1);
        }

        if (get) {
                counter_get(zname, fd);
        } else {
                counter_set(fd);
                /* let cron reschedule events */
                send_cron_msg(zname, zroot);
        }

        (void) close(fd);
}

/*
 * Get semaphore value and print.
 */
static void
counter_get(const char *zname, int fd)
{
        uint32_t counter;
        caddr_t addr;

        addr = mmap(NULL, sizeof (uint32_t), PROT_READ, MAP_SHARED, fd, 0);
        if (addr == MAP_FAILED) {
                (void) fprintf(stderr,
                    gettext("Error mapping semaphore: %s\n"),
                    strerror(errno));
                exit(1);
        }
        counter = *(uint32_t *)(uintptr_t)addr;

        (void) munmap(addr, sizeof (uint32_t));

        if (zname == NULL)
                (void) printf("%u\n", counter);
        else
                (void) printf("%-20s %u\n", zname, counter);

}

/*
 * Increment semaphore value.
 */
static void
counter_set(int fd)
{
        caddr_t addr;

        addr = mmap(NULL, sizeof (uint32_t), PROT_READ|PROT_WRITE,
            MAP_SHARED, fd, 0);
        if (addr == MAP_FAILED) {
                (void) fprintf(stderr,
                    gettext("Error mapping semaphore: %s\n"),
                    strerror(errno));
                exit(1);
        }

        /*LINTED*/
        atomic_add_32((uint32_t *)addr, 1);

        (void) munmap(addr, sizeof (uint32_t));
}

/*
 * Walk through running zones and call doit() for each zones.
 *
 * Note: we call zone_get_rootpath() indirectly using dlopen().
 * This is because tzreload resides under /sbin and needs to run
 * without /usr (ie /usr/lib/libzonecfg.so.1). The reason tzreload
 * being in /sbin is that tzreload -I may be called to create
 * /var/run/tzsync before /usr is mounted. To do that zone_get_rootpath()
 * isn't necessary. Therefore, libzonecfg is dlopen'd when required
 * rather than having static linkage to it which would make tzreload
 * unable to run without /usr.
 */
static void
walk_zones(int get)
{
        zoneid_t *zids;
        uint_t  ui, nzents, onzents;
        char    zroot[PATH_MAX + 1];
        char    zname[ZONENAME_MAX];
        char    zbrand[MAXNAMELEN];
        static int (*get_zroot)(char *, char *, size_t);

        if (getzoneid() != GLOBAL_ZONEID) {
                (void) fprintf(stderr, gettext("not in the global zone.\n"));
                exit(1);
        }

        if (get_zroot == NULL) {
                void    *hdl;

                if ((hdl = dlopen("libzonecfg.so.1", RTLD_NOW)) == NULL) {
                        (void) fprintf(stderr,
                            gettext("unable to get zone configuration.\n"));
                        exit(1);
                }
                get_zroot = (int (*)(char *, char *, size_t))
                    dlsym(hdl, "zone_get_rootpath");
                if (get_zroot == NULL) {
                        (void) fprintf(stderr,
                            gettext("unable to get zone configuration.\n"));
                        exit(1);
                }
        }

        nzents = 0;
        if (zone_list(NULL, &nzents) != 0) {
                (void) fprintf(stderr,
                    gettext("failed to get zoneid list\n"));
                exit(1);
        }

again:
        if (nzents == 0)
                return;

        if ((zids = malloc(nzents * sizeof (zoneid_t))) == NULL) {
                (void) fprintf(stderr, gettext("Insufficient memory.\n"));
                exit(1);
        }

        onzents = nzents;
        if (zone_list(zids, &nzents) != 0) {
                (void) fprintf(stderr,
                    gettext("failed to get zoneid list\n"));
                exit(1);
        }

        if (nzents != onzents) {
                /* zone increased while doing zone_list() */
                free(zids);
                goto again;
        }

        for (ui = 0; ui < nzents; ui++) {
                if (zone_getattr(zids[ui], ZONE_ATTR_BRAND, zbrand,
                    sizeof (zbrand)) < 0) {
                        (void) fprintf(stderr,
                            gettext("failed to get zone attribute\n"));
                        exit(1);
                }
                /* We only take care of native zones */
                if (strcmp(zbrand, NATIVE_BRAND_NAME) != 0)
                        continue;
                if (getzonenamebyid(zids[ui], zname, sizeof (zname)) < 0) {
                        (void) fprintf(stderr,
                            gettext("failed to get zone name\n"));
                        exit(1);
                }

                if (zids[ui] == GLOBAL_ZONEID) {
                        zroot[0] = '\0';
                } else {
                        if ((*get_zroot)(zname, zroot,
                            sizeof (zroot)) != Z_OK) {
                                (void) fprintf(stderr,
                                    gettext("failed to get zone's root\n"));
                                exit(1);
                        }
                }
                doit(zname, zroot, get);
        }
}

#include "cron.h"

/*
 * Send REFRESH event to cron.
 */
static void
send_cron_msg(const char *zname, const char *zroot)
{
        struct message msg;
        int     msgfd;
        char    fifo[PATH_MAX + 1];

        if (strlcpy(fifo, zroot, sizeof (fifo)) >= sizeof (fifo) ||
            strlcat(fifo, FIFO, sizeof (fifo)) >= sizeof (fifo)) {
                (void) fprintf(stderr, gettext("zonepath too long\n"));
                exit(1);
        }

        (void) memset(&msg, 0, sizeof (msg));
        msg.etype = REFRESH;

        if ((msgfd = open(fifo, O_WRONLY|O_NDELAY)) < 0) {
                if (errno == ENXIO || errno == ENOENT) {
                        if (zname != NULL) {
                                (void) fprintf(stderr, gettext(
                                    "cron isn't running in %s zone.\n"), zname);
                        } else {
                                (void) fprintf(stderr,
                                    gettext("cron isn't running.\n"));
                        }
                } else {
                        if (zname != NULL) {
                                (void) fprintf(stderr, gettext(
                                    "failed to send message to cron "
                                    "in %s zone.\n"), zname);
                        } else {
                                (void) fprintf(stderr, gettext(
                                    "failed to send message to cron.\n"));
                        }
                }
                return;
        }

        if (write(msgfd, &msg, sizeof (msg)) != sizeof (msg)) {
                (void) fprintf(stderr, gettext("failed to send message.\n"));
        }

        (void) close(msgfd);
}