root/usr/src/cmd/power/powerd.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 2016 Nexenta Systems, Inc. All rights reserved.
 */

#include <stdio.h>                      /* Standard */
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <dirent.h>
#include <thread.h>
#include <limits.h>
#include <sys/todio.h>                  /* Time-Of-Day chip */
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ipc.h>                    /* IPC functions */
#include <signal.h>                     /* signal handling */
#include <syslog.h>
#include <unistd.h>
#include <libdevinfo.h>
#include <poll.h>
#include <sys/pm.h>                     /* power management driver */
#include <sys/uadmin.h>
#include <sys/openpromio.h>             /* for prom access */
#include <sys/sysmacros.h>              /* for MIN & MAX macros */
#include <sys/modctl.h>
#include <sys/stropts.h>                /* for INFTIM */
#include <sys/pbio.h>
#include <sys/cpr.h>
#include <sys/srn.h>
#include <stdarg.h>

#include "powerd.h"

/* External Functions */
extern struct tm *localtime_r(const time_t *, struct tm *);
extern void sysstat_init(void);
extern int check_tty(hrtime_t *, int);
extern int check_disks(hrtime_t *, int);
extern int check_load_ave(hrtime_t *, float);
extern int check_nfs(hrtime_t *, int);
extern int last_disk_activity(hrtime_t *, int);
extern int last_tty_activity(hrtime_t *, int);
extern int last_load_ave_activity(hrtime_t *);
extern int last_nfs_activity(hrtime_t *, int);

#define PM              "/dev/pm"
#define TOD             "/dev/tod"
#define PROM            "/dev/openprom"
#define PB              "/dev/power_button"
#define SRN             "/dev/srn"
#define LOGFILE         "./powerd.log"

#define PBM_THREAD      0
#define ATTACH_THREAD   1
#define NUM_THREADS     2

#define CHECK_INTERVAL  5
#define IDLECHK_INTERVAL        15
#define MINS_TO_SECS    60
#define HOURS_TO_SECS   (60 * 60)
#define DAYS_TO_SECS    (24 * 60 * 60)
#define HOURS_TO_MINS   60
#define DAYS_TO_MINS    (24 * 60)

#define LIFETIME_SECS                   (7 * 365 * DAYS_TO_SECS)
#define DEFAULT_POWER_CYCLE_LIMIT       10000
#define DEFAULT_SYSTEM_BOARD_DATE       804582000       /* July 1, 1995 */

#define LLEN 80

typedef enum {root, options} prom_node_t;

/* State Variables */
static struct cprconfig asinfo;
static time_t           shutdown_time;  /* Time for next shutdown check */
static time_t           checkidle_time; /* Time for next idleness check */
static time_t           last_resume;
pwr_info_t              *info;          /* private as config data buffer */
static int              pb_fd;          /* power button driver */
static int              broadcast;      /* Enables syslog messages */
static int              start_calc;
static int              autoshutdown_en;
static int              do_idlecheck;
static int              got_sighup;
static int              estar_v2_prop;
static int              estar_v3_prop;
static int              log_power_cycles_error = 0;
static int              log_system_board_date_error = 0;
static int              log_no_autoshutdown_warning = 0;
static mutex_t          poweroff_mutex;

static char *autoshutdown_cmd[] = {
        "/usr/bin/sys-suspend",
        "-n", "-d", ":0", NULL
};

static char *power_button_cmd[] = {
        "/usr/bin/sys-suspend",
        "-h", "-d", ":0", NULL
};

#ifdef __x86
static char *autoS3_cmd[] = {
        "/usr/bin/sys-suspend",
        "-n", "-d", ":0", NULL
};
#endif

static char pidpath[] = PIDPATH;
static char scratch[PATH_MAX];
static char *prog;

/* Local Functions */
static void alarm_handler(int);
static void thaw_handler(int);
static void kill_handler(int);
static void work_handler(int);
static void check_shutdown(time_t *, hrtime_t *);
static void check_idleness(time_t *, hrtime_t *);
static int last_system_activity(hrtime_t *);
static int run_idlecheck(void);
static void set_alarm(time_t);
static int poweroff(const char *, char **);
static int is_ok2shutdown(time_t *);
static int get_prom(int, prom_node_t, char *, char *, size_t);
static void *power_button_monitor(void *);
static int open_pidfile(char *);
static int write_pidfile(int, pid_t);
static int read_cpr_config(void);
static void *system_activity_monitor(void *);
#ifdef __x86
static void *autos3_monitor(void *);
#endif
static void do_attach(void);
static void *attach_devices(void *);
static int powerd_debug;

/* PRINTFLIKE1 */
static void
logerror(const char *fmt, ...)
{
        va_list args;

        va_start(args, fmt);
        if (broadcast)
                vsyslog(LOG_ERR, fmt, args);
        va_end(args);
}


static void
estrcpy(char *dst, char *src, size_t dlen)
{
        size_t slen;

        slen = strlcpy(dst, src, dlen);
        if (slen >= dlen) {
                logerror("%s: string too long \"%s ...\"\n"
                    "(len %d, max %d)\n", prog, dst, slen, dlen - 1);
                exit(EXIT_FAILURE);
        }
}


int
main(int argc, char *argv[])
{
        pid_t           pid;
        int             pm_fd;
        struct sigaction act;
        sigset_t        sigmask;
        int             c;
        char            errmsg[PATH_MAX + 64];
        int             pid_fd;

        prog = argv[0];
        if (geteuid() != 0) {
                (void) fprintf(stderr, "%s: Must be root\n", prog);
                exit(EXIT_FAILURE);
        }

        if ((pid_fd = open_pidfile(prog)) ==  -1)
                exit(EXIT_FAILURE);

        /*
         * Process options
         */
        broadcast = 1;
        while ((c = getopt(argc, argv, "nd")) != EOF) {
                switch (c) {
                case 'd':
                        powerd_debug = 1;
                        break;
                case 'n':
                        broadcast = 0;
                        break;
                case '?':
                        (void) fprintf(stderr, "Usage: %s [-n]\n", prog);
                        exit(EXIT_FAILURE);
                }
        }

        pm_fd = open(PM, O_RDWR);
        if (pm_fd == -1) {
                (void) snprintf(errmsg, sizeof (errmsg), "%s: %s", prog, PM);
                perror(errmsg);
                exit(EXIT_FAILURE);
        }
        (void) close(pm_fd);

        /*
         * Initialize mutex lock used to insure only one command to
         * run at a time.
         */
        if (mutex_init(&poweroff_mutex, USYNC_THREAD, NULL) != 0) {
                (void) fprintf(stderr,
                    "%s: Unable to initialize mutex lock\n", prog);
                exit(EXIT_FAILURE);
        }

        if ((info = (pwr_info_t *)malloc(sizeof (pwr_info_t))) == NULL) {
                (void) snprintf(errmsg, sizeof (errmsg), "%s: malloc", prog);
                perror(errmsg);
                exit(EXIT_FAILURE);
        }

        /*
         * Daemon is set to go...
         */
        if ((pid = fork()) < 0)
                exit(EXIT_FAILURE);
        else if (pid != 0)
                exit(EXIT_SUCCESS);

        pid = getpid();
        openlog(prog, 0, LOG_DAEMON);
        if (write_pidfile(pid_fd, pid) == -1)   /* logs errors on failure */
                exit(EXIT_FAILURE);
        (void) close(pid_fd);

        /*
         * Close all the parent's file descriptors (Bug 1225843).
         */
        closefrom(0);
        (void) setsid();
        (void) chdir("/");
        (void) umask(0);
#ifdef DEBUG
        /*
         * Connect stdout to the console.
         */
        if (dup2(open("/dev/console", O_WRONLY|O_NOCTTY), 1) == -1) {
                logerror("Unable to connect to the console.");
        }
#endif
        info->pd_flags = PD_AC;
        info->pd_idle_time = -1;
        info->pd_start_time = 0;
        info->pd_finish_time = 0;

        /*
         * Allow SIGQUIT, SIGINT and SIGTERM signals to terminate us
         * any time
         */
        act.sa_handler = kill_handler;
        (void) sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        (void) sigaction(SIGQUIT, &act, NULL);
        (void) sigaction(SIGINT, &act, NULL);
        (void) sigaction(SIGTERM, &act, NULL);

        (void) sigfillset(&sigmask);
        (void) sigdelset(&sigmask, SIGQUIT);
        (void) sigdelset(&sigmask, SIGINT);
        (void) sigdelset(&sigmask, SIGTERM);
        (void) thr_sigsetmask(SIG_SETMASK, &sigmask, NULL);

        /*
         * If "power_button" device node can be opened, create a new
         * thread to monitor the power button.
         */
        if ((pb_fd = open(PB, O_RDONLY)) != -1) {
                if (powerd_debug)
                        logerror("powerd starting power button monitor.");
                if (thr_create(NULL, 0, power_button_monitor, NULL,
                    THR_DAEMON, NULL) != 0) {
                        logerror("Unable to monitor system's power button.");
                }
        }

        do_attach();

        /*
         * Create a new thread to monitor system activity and suspend
         * system if idle.
         */
        if (powerd_debug)
                logerror("powerd starting system activity monitor.");
        if (thr_create(NULL, 0, system_activity_monitor, NULL,
            THR_DAEMON, NULL) != 0) {
                logerror("Unable to create thread to monitor system activity.");
        }

#ifdef __x86
        /*
         * Create a new thread to handle autos3 trigger
         */
        if (powerd_debug)
                logerror("powerd starting autos3 monitor.");
        if (thr_create(NULL, 0, autos3_monitor, NULL, THR_DAEMON,
            NULL) != 0) {
                logerror("Unable to create thread to monitor autos3 activity.");
        }
#endif

        /*
         * Block until we receive an explicit terminate signal
         */
        (void) sigsuspend(&sigmask);

        return (1);
}

static void *
system_activity_monitor(void *arg __unused)
{
        struct sigaction act;
        sigset_t sigmask;

        /*
         * Setup for gathering system's statistic.
         */
        sysstat_init();

        /*
         * In addition to the SIGQUIT, SIGINT and SIGTERM signals already
         * being handled, this thread also needs to handle SIGHUP, SIGALRM
         * and SIGTHAW signals.
         */
        (void) sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        act.sa_handler = alarm_handler;
        (void) sigaction(SIGALRM, &act, NULL);
        act.sa_handler = work_handler;
        (void) sigaction(SIGHUP, &act, NULL);
        act.sa_handler = thaw_handler;
        (void) sigaction(SIGTHAW, &act, NULL);

        /*
         * Invoke work_handler with a dummy SIGHUP signal to read
         * cpr config file, get autoshutdown properties and schedule
         * an alarm if needed.
         */
        work_handler(SIGHUP);

        /*
         * Wait for signal to read file
         */
        (void) thr_sigsetmask(0, 0, &sigmask);
        (void) sigdelset(&sigmask, SIGHUP);
        (void) sigdelset(&sigmask, SIGALRM);
        (void) sigdelset(&sigmask, SIGTHAW);
        (void) thr_sigsetmask(SIG_SETMASK, &sigmask, NULL);
        do {
                (void) sigsuspend(&sigmask);
        } while (errno == EINTR);
        return (NULL);
}

#ifdef __x86
static void *
autos3_monitor(void *arg __unused)
{
        struct pollfd poll_fd;
        srn_event_info_t srn_event;             /* contains suspend type */
        int fd, ret;

        fd = open(SRN, O_RDWR|O_EXCL|O_NDELAY);
        if (fd == -1) {
                logerror("Unable to open %s: %s", SRN, strerror(errno));
                thr_exit((void *)(intptr_t)errno);
        }

        /*
         * Tell device we want the special sauce
         */
        ret = ioctl(fd, SRN_IOC_AUTOSX, NULL);
        if (ret == -1) {
                logerror("Ioctl SRN_IOC_AUTOSX failed: %s", strerror(errno));
                (void) close(fd);
                thr_exit((void *)(intptr_t)errno);
        }
        poll_fd.fd = fd;
        /*CONSTCOND*/
        while (1) {
                poll_fd.revents = 0;
                poll_fd.events = POLLIN;
                if (poll(&poll_fd, 1, -1) < 0) {
                        switch (errno) {
                        case EINTR:
                        case EAGAIN:
                                continue;
                        default:
                                logerror("Poll error: %s", strerror(errno));
                                (void) close(fd);
                                thr_exit((void *)(intptr_t)errno);
                        }
                }

                ret = ioctl(fd, SRN_IOC_NEXTEVENT, &srn_event);
                if (ret == -1) {
                        logerror("ioctl error: %s", strerror(errno));
                        (void) close(fd);
                        thr_exit((void *)(intptr_t)errno);
                }
                switch (srn_event.ae_type) {
                case 3:                 /* S3 */
                        if (powerd_debug)
                                logerror("ioctl returns type: %d",
                                    srn_event.ae_type);
                        break;
                default:
                        logerror("Unsupported target state %d",
                            srn_event.ae_type);
                        continue;
                }
                (void) poweroff("AutoS3", autoS3_cmd);
                continue;
        }
        return (NULL);
}
#endif

static int
read_cpr_config(void)
{
        int     asfd;

        if ((asfd = open(CPR_CONFIG, O_RDONLY)) < 0) {
                logerror("Unable to open CPR config file '%s'", CPR_CONFIG);
                return (-1);
        }

        if (read(asfd, (void *)&asinfo, sizeof (asinfo)) != sizeof (asinfo)) {
                logerror("Unable to read CPR config file '%s'", CPR_CONFIG);
                (void) close(asfd);
                return (-1);
        }

        (void) close(asfd);

        return (0);
}

/*ARGSUSED*/
static void
thaw_handler(int sig)
{
        start_calc  = 0;
        last_resume = time(NULL);
}

/*ARGSUSED*/
static void
kill_handler(int sig)
{
        int ret_code = EXIT_SUCCESS;

        /*
         * Free resources
         */

        free(info);
        if (pb_fd != -1) {
                (void) close(pb_fd);
        }
        (void) mutex_destroy(&poweroff_mutex);
        (void) unlink(pidpath);
        closelog();
        exit(ret_code);
}

/*ARGSUSED*/
static void
alarm_handler(int sig)
{
        time_t          now;
        hrtime_t        hr_now;

        now = time(NULL);
        hr_now = gethrtime();
        if (checkidle_time <= now && checkidle_time != 0)
                check_idleness(&now, &hr_now);
        if (shutdown_time <= now && shutdown_time != 0)
                check_shutdown(&now, &hr_now);

        set_alarm(now);
}

/*ARGSUSED*/
static void
work_handler(int sig)
{
        time_t          now;
        hrtime_t        hr_now;
        struct stat     stat_buf;

        do_idlecheck = 0;
        info->pd_flags = PD_AC;

        /*
         * Parse the config file for autoshutdown and idleness entries.
         */
        if (read_cpr_config() < 0)
                return;

        /*
         * Since Oct. 1, 1995, any new system shipped had root
         * property "energystar-v2" defined in its prom.  Systems
         * shipped after July 1, 1999, will have "energystar-v3"
         * property.
         */
        estar_v2_prop = asinfo.is_cpr_default;

        info->pd_flags |= asinfo.is_autowakeup_capable;

        if (strlen(asinfo.idlecheck_path) > 0) {
                if (stat(asinfo.idlecheck_path, &stat_buf) != 0) {
                        logerror("unable to access idlecheck program \"%s\".",
                            asinfo.idlecheck_path);
                } else if (!(stat_buf.st_mode & S_IXUSR)) {
                        logerror("idlecheck program \"%s\" is not executable.",
                            asinfo.idlecheck_path);
                } else {
                        do_idlecheck = 1;
                }
        }

        if (strlen(asinfo.as_behavior) == 0 ||
            strcmp(asinfo.as_behavior, "noshutdown") == 0 ||
            strcmp(asinfo.as_behavior, "unconfigured") == 0) {
                info->pd_autoshutdown = 0;
        } else if (strcmp(asinfo.as_behavior, "default") == 0) {
                info->pd_autoshutdown = estar_v2_prop;
        } else if (strcmp(asinfo.as_behavior, "shutdown") == 0 ||
            strcmp(asinfo.as_behavior, "autowakeup") == 0) {
                info->pd_autoshutdown = asinfo.is_cpr_capable;
        } else {
                logerror("autoshutdown behavior \"%s\" unrecognized.",
                    asinfo.as_behavior);
                info->pd_autoshutdown = 0;
        }

        if (info->pd_autoshutdown) {
                info->pd_idle_time = asinfo.as_idle;
                info->pd_start_time =
                    (asinfo.as_sh * 60 + asinfo.as_sm) % DAYS_TO_MINS;
                info->pd_finish_time =
                    (asinfo.as_fh * 60 + asinfo.as_fm) % DAYS_TO_MINS;
                info->pd_autoresume =
                    (strcmp(asinfo.as_behavior, "autowakeup") == 0) ? 1 : 0;
        }
        autoshutdown_en = (asinfo.as_idle >= 0 && info->pd_autoshutdown)
            ? 1 : 0;

#ifdef DEBUG
        (void) fprintf(stderr, "autoshutdown_en = %d, as_idle = %d, "
            "pd_autoresume = %d\n",
            autoshutdown_en, asinfo.as_idle, info->pd_autoresume);

        (void) fprintf(stderr, " pd_start_time=%d, pd_finish_time=%d\n",
            info->pd_start_time, info->pd_finish_time);
#endif

        got_sighup = 1;
        now = last_resume = time(NULL);
        hr_now = gethrtime();
        check_idleness(&now, &hr_now);
        check_shutdown(&now, &hr_now);
        set_alarm(now);
}

static void
check_shutdown(time_t *now, hrtime_t *hr_now)
{
        int             tod_fd = -1;
        int             kbd, mouse, system, least_idle, idlecheck_time;
        int             next_time;
        int             s, f;
        struct tm       tmp_time;
        time_t          start_of_day, time_since_last_resume;
        time_t          wakeup_time;
        extern long     conskbd_idle_time(void);
        extern long     consms_idle_time(void);
        static int      warned_kbd, warned_ms; /* print error msg one time */

        if (!autoshutdown_en) {
                shutdown_time = 0;
                return;
        }

        (void) localtime_r(now, &tmp_time);
        tmp_time.tm_sec = 0;
        tmp_time.tm_min = 0;
        tmp_time.tm_hour = 0;
        start_of_day = mktime(&tmp_time);
        s = start_of_day + info->pd_start_time * 60;
        f = start_of_day + info->pd_finish_time * 60;
        if ((s < f && *now >= s && *now < f) ||
            (s >= f && (*now < f || *now >= s))) {
                if ((mouse = (int)consms_idle_time()) < 0) {
                        if (! warned_ms) {
                                warned_ms = 1;
                                logerror("powerd: failed to get "
                                    "idle time for console mouse");
                        }
                        return;
                }
                if ((kbd = (int)conskbd_idle_time()) < 0) {
                        if (! warned_kbd) {
                                warned_kbd = 1;
                                logerror("powerd: failed to get "
                                    "idle time for console keyboard");
                        }
                        return;
                }

                system = last_system_activity(hr_now);
                /* who is the last to go idle */
                least_idle = MIN(system, MIN(kbd, mouse));

                /*
                 * Calculate time_since_last_resume and the next_time
                 * to auto suspend.
                 */
                start_calc = 1;
                time_since_last_resume = time(NULL) - last_resume;
                next_time = info->pd_idle_time * 60 -
                    MIN(least_idle, time_since_last_resume);

#ifdef DEBUG
                fprintf(stderr, " check_shutdown: next_time=%d\n", next_time);
#endif

                /*
                 * If we have get the SIGTHAW signal at this point - our
                 * calculation of time_since_last_resume is wrong  so
                 * - we need to recalculate.
                 */
                while (start_calc == 0) {
                        /* need to redo calculation */
                        start_calc = 1;
                        time_since_last_resume = time(NULL) - last_resume;
                        next_time = info->pd_idle_time * 60 -
                            MIN(least_idle, time_since_last_resume);
                }

                /*
                 * Only when everything else is idle, run the user's idlecheck
                 * script.
                 */
                if (next_time <= 0 && do_idlecheck) {
                        got_sighup = 0;
                        idlecheck_time = run_idlecheck();
                        next_time = info->pd_idle_time * 60 -
                            MIN(idlecheck_time, MIN(least_idle,
                            time_since_last_resume));
                        /*
                         * If we have caught SIGTHAW or SIGHUP, need to
                         * recalculate.
                         */
                        while (start_calc == 0 || got_sighup == 1) {
                                start_calc = 1;
                                got_sighup = 0;
                                idlecheck_time = run_idlecheck();
                                time_since_last_resume = time(NULL) -
                                    last_resume;
                                next_time = info->pd_idle_time * 60 -
                                    MIN(idlecheck_time, MIN(least_idle,
                                    time_since_last_resume));
                        }
                }

                if (next_time <= 0) {
                        if (is_ok2shutdown(now)) {
                                /*
                                 * Setup the autowakeup alarm.  Clear it
                                 * right after poweroff, just in case if
                                 * shutdown doesn't go through.
                                 */
                                if (info->pd_autoresume)
                                        tod_fd = open(TOD, O_RDWR);
                                if (info->pd_autoresume && tod_fd != -1) {
                                        wakeup_time = (*now < f) ? f :
                                            (f + DAYS_TO_SECS);
                                        /*
                                         * A software fix for hardware
                                         * bug 1217415.
                                         */
                                        if ((wakeup_time - *now) < 180) {
                                                logerror(
                "Since autowakeup time is less than 3 minutes away, "
                "autoshutdown will not occur.");
                                                shutdown_time = *now + 180;
                                                (void) close(tod_fd);
                                                return;
                                        }
                                        if (ioctl(tod_fd, TOD_SET_ALARM,
                                            &wakeup_time) == -1) {
                                                logerror("Unable to program TOD"
                                                    " alarm for autowakeup.");
                                                (void) close(tod_fd);
                                                return;
                                        }
                                }

                                (void) poweroff("Autoshutdown",
                                    autoshutdown_cmd);

                                if (info->pd_autoresume && tod_fd != -1) {
                                        if (ioctl(tod_fd, TOD_CLEAR_ALARM,
                                            NULL) == -1)
                                                logerror("Unable to clear "
                                                    "alarm in TOD device.");
                                        (void) close(tod_fd);
                                }

                                (void) time(now);
                                /* wait at least 5 mins */
                                shutdown_time = *now +
                                    ((info->pd_idle_time * 60) > 300 ?
                                    (info->pd_idle_time * 60) : 300);
                        } else {
                                /* wait 5 mins */
                                shutdown_time = *now + 300;
                        }
                } else
                        shutdown_time = *now + next_time;
        } else if (s < f && *now >= f) {
                shutdown_time = s + DAYS_TO_SECS;
        } else
                shutdown_time = s;
}

static int
is_ok2shutdown(time_t *now)
{
        int     prom_fd = -1;
        char    power_cycles_st[LLEN];
        char    power_cycle_limit_st[LLEN];
        char    system_board_date_st[LLEN];
        int     power_cycles, power_cycle_limit, free_cycles, scaled_cycles;
        time_t  life_began, life_passed;
        int     no_power_cycles = 0;
        int     no_system_board_date = 0;
        int     ret = 1;

        /* CONSTCOND */
        while (1) {
                if ((prom_fd = open(PROM, O_RDWR)) == -1 &&
                    (errno == EAGAIN))
                        continue;
                break;
        }

        /*
         * when #power-cycles property does not exist
         * power cycles are unlimited.
         */
        if (get_prom(prom_fd, options, "#power-cycles",
            power_cycles_st, sizeof (power_cycles_st)) == 0)
                goto ckdone;

        if (get_prom(prom_fd, root, "power-cycle-limit",
            power_cycle_limit_st, sizeof (power_cycle_limit_st)) == 0) {
                power_cycle_limit = DEFAULT_POWER_CYCLE_LIMIT;
        } else {
                power_cycle_limit = atoi(power_cycle_limit_st);
        }

        /*
         * Allow 10% of power_cycle_limit as free cycles.
         */
        free_cycles = power_cycle_limit / 10;

        power_cycles = atoi(power_cycles_st);
        if (power_cycles < 0)
                no_power_cycles++;
        else if (power_cycles <= free_cycles)
                goto ckdone;

        if (no_power_cycles && log_power_cycles_error == 0) {
                logerror("Invalid PROM property \"#power-cycles\" was found.");
                log_power_cycles_error++;
        }

        if (get_prom(prom_fd, options, "system-board-date",
            system_board_date_st, sizeof (system_board_date_st)) == 0) {
                no_system_board_date++;
        } else {
                life_began = strtol(system_board_date_st, (char **)NULL, 16);
                if (life_began > *now) {
                        no_system_board_date++;
                }
        }
        if (no_system_board_date) {
                if (log_system_board_date_error == 0) {
                        logerror("No or invalid PROM property "
                            "\"system-board-date\" was found.");
                        log_system_board_date_error++;
                }
                life_began = DEFAULT_SYSTEM_BOARD_DATE;
        }

        life_passed = *now - life_began;

        /*
         * Since we don't keep the date that last free_cycle is ended, we
         * need to spread (power_cycle_limit - free_cycles) over the entire
         * 7-year life span instead of (lifetime - date free_cycles ended).
         */
        scaled_cycles = (int)(((float)life_passed / (float)LIFETIME_SECS) *
            (power_cycle_limit - free_cycles));

        if (no_power_cycles)
                goto ckdone;

#ifdef DEBUG
        (void) fprintf(stderr, "Actual power_cycles = %d\t"
            "Scaled power_cycles = %d\n", power_cycles, scaled_cycles);
#endif
        if (power_cycles > scaled_cycles) {
                if (log_no_autoshutdown_warning == 0) {
                        logerror("Automatic shutdown has been temporarily "
                            "suspended in order to preserve the reliability "
                            "of this system.");
                        log_no_autoshutdown_warning++;
                }
                ret = 0;
                goto ckdone;
        }

ckdone:
        if (prom_fd != -1)
                (void) close(prom_fd);
        return (ret);
}

static void
check_idleness(time_t *now, hrtime_t *hr_now)
{

        /*
         * Check idleness only when autoshutdown is enabled.
         */
        if (!autoshutdown_en) {
                checkidle_time = 0;
                return;
        }

        info->pd_ttychars_idle = check_tty(hr_now, asinfo.ttychars_thold);
        info->pd_loadaverage_idle =
            check_load_ave(hr_now, asinfo.loadaverage_thold);
        info->pd_diskreads_idle = check_disks(hr_now, asinfo.diskreads_thold);
        info->pd_nfsreqs_idle = check_nfs(hr_now, asinfo.nfsreqs_thold);

#ifdef DEBUG
        (void) fprintf(stderr, "Idle ttychars for %d secs.\n",
            info->pd_ttychars_idle);
        (void) fprintf(stderr, "Idle loadaverage for %d secs.\n",
            info->pd_loadaverage_idle);
        (void) fprintf(stderr, "Idle diskreads for %d secs.\n",
            info->pd_diskreads_idle);
        (void) fprintf(stderr, "Idle nfsreqs for %d secs.\n",
            info->pd_nfsreqs_idle);
#endif

        checkidle_time = *now + IDLECHK_INTERVAL;
}

static int
last_system_activity(hrtime_t *hr_now)
{
        int     act_idle, latest;

        latest = info->pd_idle_time * 60;
        act_idle = last_tty_activity(hr_now, asinfo.ttychars_thold);
        latest = MIN(latest, act_idle);
        act_idle = last_load_ave_activity(hr_now);
        latest = MIN(latest, act_idle);
        act_idle = last_disk_activity(hr_now, asinfo.diskreads_thold);
        latest = MIN(latest, act_idle);
        act_idle = last_nfs_activity(hr_now, asinfo.nfsreqs_thold);
        latest = MIN(latest, act_idle);

        return (latest);
}

static int
run_idlecheck()
{
        char            pm_variable[LLEN];
        char            *cp;
        int             status;
        pid_t           child;

        /*
         * Reap any child process which has been left over.
         */
        while (waitpid((pid_t)-1, &status, WNOHANG) > 0)
                ;

        /*
         * Execute the user's idlecheck script and set variable PM_IDLETIME.
         * Returned exit value is the idle time in minutes.
         */
        if ((child = fork1()) == 0) {
                (void) sprintf(pm_variable, "PM_IDLETIME=%d",
                    info->pd_idle_time);
                (void) putenv(pm_variable);
                cp = strrchr(asinfo.idlecheck_path, '/');
                if (cp == NULL)
                        cp = asinfo.idlecheck_path;
                else
                        cp++;
                (void) execl(asinfo.idlecheck_path, cp, NULL);
                exit(-1);
        } else if (child == -1) {
                return (info->pd_idle_time * 60);
        }

        /*
         * Wait until the idlecheck program completes.
         */
        if (waitpid(child, &status, 0) != child) {
                /*
                 * We get here if the calling process gets a signal.
                 */
                return (info->pd_idle_time * 60);
        }

        if (WEXITSTATUS(status) < 0) {
                return (info->pd_idle_time * 60);
        } else {
                return (WEXITSTATUS(status) * 60);
        }
}

static void
set_alarm(time_t now)
{
        time_t  itime, stime, next_time, max_time;
        int     next_alarm;

        max_time = MAX(checkidle_time, shutdown_time);
        if (max_time == 0) {
                (void) alarm(0);
                return;
        }
        itime = (checkidle_time == 0) ? max_time : checkidle_time;
        stime = (shutdown_time == 0) ? max_time : shutdown_time;
        next_time = MIN(itime, stime);
        next_alarm = (next_time <= now) ? 1 : (next_time - now);
        (void) alarm(next_alarm);

#ifdef DEBUG
        (void) fprintf(stderr, "Currently @ %s", ctime(&now));
        (void) fprintf(stderr, "Checkidle in %d secs\n", checkidle_time - now);
        (void) fprintf(stderr, "Shutdown  in %d secs\n", shutdown_time - now);
        (void) fprintf(stderr, "Next alarm goes off in %d secs\n", next_alarm);
        (void) fprintf(stderr, "************************************\n");
#endif
}

static int
poweroff(const char *msg, char **cmd_argv)
{
        struct stat     statbuf;
        pid_t           pid, child;
        struct passwd   *pwd;
        char            *home, *user;
        char            ehome[] = "HOME=";
        char            euser[] = "LOGNAME=";
        int             status;
        char            **ca;

        if (mutex_trylock(&poweroff_mutex) != 0)
                return (0);

        if (stat("/dev/console", &statbuf) == -1 ||
            (pwd = getpwuid(statbuf.st_uid)) == NULL) {
                (void) mutex_unlock(&poweroff_mutex);
                return (1);
        }

        if (msg)
                syslog(LOG_NOTICE, msg);

        if (*cmd_argv == NULL) {
                logerror("No command to run.");
                (void) mutex_unlock(&poweroff_mutex);
                return (1);
        }

        home = malloc(strlen(pwd->pw_dir) + sizeof (ehome));
        user = malloc(strlen(pwd->pw_name) + sizeof (euser));
        if (home == NULL || user == NULL) {
                free(home);
                free(user);
                logerror("No memory.");
                (void) mutex_unlock(&poweroff_mutex);
                return (1);
        }
        (void) strcpy(home, ehome);
        (void) strcat(home, pwd->pw_dir);
        (void) strcpy(user, euser);
        (void) strcat(user, pwd->pw_name);

        /*
         * Need to simulate the user enviroment, minimaly set HOME, and USER.
         */
        if ((child = fork1()) == 0) {
                (void) putenv(home);
                (void) putenv(user);
                (void) setgid(pwd->pw_gid);
                (void) setuid(pwd->pw_uid);

                /*
                 * check for shutdown flag and set environment
                 */
                for (ca = cmd_argv; *ca; ca++) {
                        if (strcmp("-h", *ca) == 0) {
                                (void) putenv("SYSSUSPENDDODEFAULT=");
                                break;
                        }
                }

                (void) execv(cmd_argv[0], cmd_argv);
                exit(EXIT_FAILURE);
        } else {
                free(home);
                free(user);
                if (child == -1) {
                        (void) mutex_unlock(&poweroff_mutex);
                        return (1);
                }
        }
        pid = 0;
        while (pid != child)
                pid = wait(&status);
        if (WEXITSTATUS(status)) {
                (void) syslog(LOG_ERR, "Failed to exec \"%s\".", cmd_argv[0]);
                (void) mutex_unlock(&poweroff_mutex);
                return (1);
        }

        (void) mutex_unlock(&poweroff_mutex);
        return (0);
}

#define PBUFSIZE        256

/*
 * Gets the value of a prom property at either root or options node.  It
 * returns 1 if it is successful, otherwise it returns 0 .
 */
static int
get_prom(int prom_fd, prom_node_t node_name,
    char *property_name, char *property_value, size_t len)
{
        union {
                char buf[PBUFSIZE + sizeof (uint_t)];
                struct openpromio opp;
        } oppbuf;
        register struct openpromio *opp = &(oppbuf.opp);
        int     got_it = 0;

        if (prom_fd == -1) {
                return (0);
        }

        switch (node_name) {
        case root:
                (void) memset(oppbuf.buf, 0, PBUFSIZE);
                opp->oprom_size = PBUFSIZE;
                if (ioctl(prom_fd, OPROMNEXT, opp) < 0) {
                        return (0);
                }

                /*
                 * Passing null string will give us the first property.
                 */
                (void) memset(oppbuf.buf, 0, PBUFSIZE);
                do {
                        opp->oprom_size = PBUFSIZE;
                        if (ioctl(prom_fd, OPROMNXTPROP, opp) < 0) {
                                return (0);
                        }
                        if (strcmp(opp->oprom_array, property_name) == 0) {
                                got_it++;
                                break;
                        }
                } while (opp->oprom_size > 0);

                if (!got_it) {
                        return (0);
                }
                if (got_it && property_value == NULL) {
                        return (1);
                }
                opp->oprom_size = PBUFSIZE;
                if (ioctl(prom_fd, OPROMGETPROP, opp) < 0) {
                        return (0);
                }
                if (opp->oprom_size == 0) {
                        *property_value = '\0';
                } else {
                        estrcpy(property_value, opp->oprom_array, len);
                }
                break;
        case options:
                estrcpy(opp->oprom_array, property_name, PBUFSIZE);
                opp->oprom_size = PBUFSIZE;
                if (ioctl(prom_fd, OPROMGETOPT, opp) < 0) {
                        return (0);
                }
                if (opp->oprom_size == 0) {
                        return (0);
                }
                if (property_value != NULL) {
                        estrcpy(property_value, opp->oprom_array, len);
                }
                break;
        default:
                logerror("Only root node and options node are supported.\n");
                return (0);
        }

        return (1);
}

#define isspace(ch)     ((ch) == ' ' || (ch) == '\t')
#define iseol(ch)       ((ch) == '\n' || (ch) == '\r' || (ch) == '\f')

/*ARGSUSED*/
static void *
power_button_monitor(void *arg)
{
        struct pollfd pfd;
        int events, ret;

        if (ioctl(pb_fd, PB_BEGIN_MONITOR, NULL) == -1) {
                logerror("Failed to monitor the power button.");
                thr_exit((void *) 0);
        }

        pfd.fd = pb_fd;
        pfd.events = POLLIN;

        /*CONSTCOND*/
        while (1) {
                if (poll(&pfd, 1, INFTIM) == -1) {
                        logerror("Failed to poll for power button events.");
                        thr_exit((void *) 0);
                }

                if (!(pfd.revents & POLLIN))
                        continue;

                /*
                 * Monitor the power button, but only take action if
                 * gnome-power-manager is not running.
                 *
                 * ret greater than 0 means could not find process.
                 */
                ret = system("/usr/bin/pgrep -fx gnome-power-manager");

                if (ioctl(pfd.fd, PB_GET_EVENTS, &events) == -1) {
                        logerror("Failed to get power button events.");
                        thr_exit((void *) 0);
                }

                if ((ret > 0) && (events & PB_BUTTON_PRESS) &&
                    (poweroff(NULL, power_button_cmd) != 0)) {
                        logerror("Power button is pressed, powering "
                            "down the system!");

                        /*
                         * Send SIGPWR signal to the init process to
                         * shut down the system.
                         */
                        if (kill(1, SIGPWR) == -1)
                                (void) uadmin(A_SHUTDOWN, AD_POWEROFF, 0);
                }

                /*
                 * Clear any power button event that has happened
                 * meanwhile we were busy processing the last one.
                 */
                if (ioctl(pfd.fd, PB_GET_EVENTS, &events) == -1) {
                        logerror("Failed to get power button events.");
                        thr_exit((void *) 0);
                }
        }
        return (NULL);
}

static void
do_attach(void)
{
        if (read_cpr_config() < 0)
                return;

        /*
         * If autopm behavior is explicitly enabled for energystar-v2, or
         * set to default for energystar-v3, create a new thread to attach
         * all devices.
         */
        estar_v3_prop = asinfo.is_autopm_default;
        if ((strcmp(asinfo.apm_behavior, "enable") == 0) ||
            (estar_v3_prop && strcmp(asinfo.apm_behavior, "default") == 0)) {
                if (powerd_debug)
                        logerror("powerd starting device attach thread.");
                if (thr_create(NULL, 0, attach_devices, NULL,
                    THR_DAEMON, NULL) != 0) {
                        logerror("Unable to create thread to attach devices.");
                }
        }
}

/*ARGSUSED*/
static void *
attach_devices(void *arg)
{
        di_node_t root_node;

        (void) sleep(60);       /* let booting finish first */

        if ((root_node = di_init("/", DINFOFORCE)) == DI_NODE_NIL) {
                logerror("Failed to attach devices.");
                return (NULL);
        }
        di_fini(root_node);

        /*
         * Unload all the modules.
         */
        (void) modctl(MODUNLOAD, 0);

        return (NULL);
}


/*
 * Create a file which will contain our pid.  Pmconfig will check this file
 * to see if we are running and can use the pid to signal us.  Returns the
 * file descriptor if successful, -1 otherwise.
 *
 * Note: Deal with attempt to launch multiple instances and also with existence
 * of an obsolete pid file caused by an earlier abort.
 */
static int
open_pidfile(char *me)
{
        int fd;
        const char *e1 = "%s: Cannot open pid file for read: ";
        const char *e2 = "%s: Cannot unlink obsolete pid file: ";
        const char *e3 = "%s: Either another daemon is running or the"
            " process is defunct (pid %d). \n";
        const char *e4 = "%s: Cannot create pid file: ";

again:
        if ((fd = open(pidpath, O_CREAT | O_EXCL | O_WRONLY, 0444)) == -1) {
                if (errno  == EEXIST) {
                        FILE *fp;
                        pid_t pid;

                        if ((fp = fopen(pidpath, "r")) == NULL) {
                                (void) fprintf(stderr, e1, me);
                                perror(NULL);
                                return (-1);
                        }

                        /* Read the pid */
                        pid = (pid_t)-1;
                        (void) fscanf(fp, "%ld", &pid);
                        (void) fclose(fp);
                        if (pid == -1) {
                                if (unlink(pidpath) == -1) {
                                        (void) fprintf(stderr, e2, me);
                                        perror(NULL);
                                        return (-1);
                                } else /* try without corrupted file */
                                        goto again;
                        }

                        /* Is pid for a running process */
                        if (kill(pid, 0) == -1) {
                                if (errno == ESRCH) {
                                        if (unlink(pidpath) == -1) {
                                                (void) fprintf(stderr, e2, me);
                                                perror(NULL);
                                                return (-1);
                                        } else  /* try without obsolete file */
                                                goto again;
                                }
                        } else {    /* powerd deamon still running or defunct */
                                (void) fprintf(stderr, e3, me, pid);
                                return (-1);
                        }

                } else {        /* create failure not due to existing file */
                        (void) fprintf(stderr, e4, me);
                        perror(NULL);
                        return (-1);
                }
        }

        (void) fchown(fd, (uid_t)-1, (gid_t)0);
        return (fd);
}

/*
 * Write a pid to the pid file.  Report errors to syslog.
 *
 */
static int
write_pidfile(int fd, pid_t pid)
{
        int     len;
        int     rc = 0;                 /* assume success */

        len = sprintf(scratch, "%ld\n", pid);
        if (write(fd, scratch, len) != len) {
                logerror("Cannot write pid file: %s", strerror(errno));
                rc = -1;
        }

        return (rc);
}