/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This file contains functions that allow applications to roll the log.
* It is intended for use by applications that open a raw device with the
* understanding that it contains a Unix File System.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/filio.h>
#include <sys/mnttab.h>
#include <sys/mntent.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fs/ufs_mount.h>
#include <sys/fs/ufs_log.h>
#include <libintl.h>
#include "roll_log.h"
/*
* The following is the template string passed to mktemp(3C). This
* string is used as the name of a temporary mount point which is
* used to roll the log.
*/
#define RLG_TEMPLATE ".rlg.XXXXXX"
#define SYSERR (-1)
#define RLM_RW 0
#define RLM_RO 1
/*
* Structure definitions:
*/
typedef struct log_info {
char *li_blkname; /* Path of block device. */
char *li_mntpoint; /* Path of mounted device. */
char *li_tmpmp_parent; /* Temporary parent directory of mount point */
char *li_tmpmp; /* Temporary mount point. */
} log_info_t;
/*
* Static function declarations:
*/
static rl_result_t is_mounted(log_info_t *lip, char *dev);
static void cleanup(log_info_t *lip);
static rl_result_t make_mp(log_info_t *lip);
static rl_result_t rlflush(log_info_t *lip);
static rl_result_t rlmount(log_info_t *lip, int mntopt);
static rl_result_t rlumount(log_info_t *lip);
/*
* NAME
* rl_roll_log
*
* SYNOPSIS
* rl_roll_log(block_dev)
*
* DESCRIPTION
* Roll the log for the block device "block_dev".
*/
rl_result_t
rl_roll_log(char *bdev)
{
log_info_t li;
rl_result_t rv = RL_SUCCESS;
(void) memset((void *)&li, 0, (size_t)sizeof (li));
if (is_mounted(&li, bdev) == RL_TRUE) {
rv = rlflush(&li);
} else {
/*
* Device appears not to be mounted.
* We need to mount the device read only.
* This automatically causes the log to be rolled, then we can
* unmount the device again. To do the mount, we need to
* create a temporary directory, and then remove it when we
* are done.
*/
rv = make_mp(&li);
switch (rv) {
case RL_CORRUPT:
/* corrupt mnttab - the file sys really was mounted */
rv = rlflush(&li);
break;
case RL_SUCCESS:
rv = rlmount(&li, RLM_RO);
if (rv == RL_SUCCESS) {
rv = rlflush(&li);
if (umount(li.li_blkname) == SYSERR) {
(void) fprintf(stderr,
"WARNING: rl_roll_log(): Can't unmount %s\n", li.li_blkname);
}
}
break;
}
}
cleanup(&li);
return (rv);
}
/*
* Static function definitions:
*/
/*
* NAME
* cleanup
*
* SYNOPSIS
* cleanup(log_infop)
*
* DESCRIPTION
* Remove the temporary mount directroy and free the dynamically
* allocated memory that is pointed to by log_infop.
*/
static void
cleanup(log_info_t *lip)
{
if (lip->li_blkname != (char *)NULL) {
free(lip->li_blkname);
lip->li_blkname = (char *)NULL;
}
if (lip->li_mntpoint != (char *)NULL) {
free(lip->li_mntpoint);
lip->li_mntpoint = (char *)NULL;
}
if (lip->li_tmpmp != (char *)NULL) {
(void) rmdir(lip->li_tmpmp);
free(lip->li_tmpmp);
lip->li_tmpmp = (char *)NULL;
}
if (lip->li_tmpmp_parent != (char *)NULL) {
(void) rmdir(lip->li_tmpmp_parent);
free(lip->li_tmpmp_parent);
lip->li_tmpmp_parent = (char *)NULL;
}
}
/*
* NAME
* is_mounted
*
* SYNOPSIS
* is_mounted(log_infop, dev)
*
* DESCRIPTION
* Determine if device dev is mounted, and return RL_TRUE if it is.
* As a side effect, li_blkname is set to point the the full path
* names of the block device. Memory for this path is dynamically
* allocated and must be freed by the caller.
*/
extern char *getfullblkname(char *);
static rl_result_t
is_mounted(log_info_t *lip, char *dev)
{
struct mnttab mntbuf;
FILE *mnttable;
rl_result_t rv = RL_FALSE;
/* Make sure that we have the full path name. */
lip->li_blkname = getfullblkname(dev);
if (lip->li_blkname == NULL)
lip->li_blkname = strdup(dev);
/* Search mnttab to see if it device is mounted. */
if ((mnttable = fopen(MNTTAB, "r")) == NULL)
return (rv);
while (getmntent(mnttable, &mntbuf) == NULL) {
if (strcmp(mntbuf.mnt_fstype, MNTTYPE_UFS) == 0) {
/* Entry is UFS */
if ((strcmp(mntbuf.mnt_mountp, dev) == 0) ||
(strcmp(mntbuf.mnt_special, lip->li_blkname)
== 0) ||
(strcmp(mntbuf.mnt_special, dev) == 0)) {
lip->li_mntpoint = strdup(mntbuf.mnt_mountp);
rv = RL_TRUE;
break;
}
}
}
(void) fclose(mnttable);
return (rv);
}
/*
* NAME
* make_mp
*
* SYNOPSIS
* make_mp(loginfop)
*
* DESCRIPTION
* Create a temporary directory to be used as a mount point. li_tmpmp
* will be set to the path of the mount point. li_tmpmp_parent is the
* parent directory of the mount point. The parent directory is
* created with restrictive permissions. Memory pointed to by
* li_tmpmp and li_tmpmp_parent should be freed by the caller.
*/
static rl_result_t
make_mp(log_info_t *lip)
{
size_t i;
rl_result_t rv = RL_FAIL;
/*
* Note tmp_dir_list[] should all be directories in the
* original root file system.
*/
static const char *tmp_dir_list[] = {
"/tmp/",
"/var/tmp/",
"/",
};
char dirname[] = RLG_TEMPLATE;
char tmp_dir[MAXPATHLEN + 1];
char mountpt_dir[MAXPATHLEN + 1];
static size_t list_len = sizeof (tmp_dir_list) /
sizeof (const char *);
int merr = 0;
/*
* Sequence of events:
* - Create a random name using mktemp(3C) (e.g., ".rlg.123456")
* - Cycle through tmp_dir_list to find a path where we can create
* a temporary parent directory (e.g., /tmp/.rlg.123456) with
* restrictive permissions. This prevents any non-root processes,
* such as a 'find', from wandering in where it doesn't belong.
* - Create the mount-point (/tmp/.rlg.123456/.rlg.123456).
*/
(void) mktemp(dirname);
for (i = 0; i < list_len; i++) {
/* Make the directory containing the mount-point */
(void) snprintf(tmp_dir, sizeof (tmp_dir), "%s%s",
tmp_dir_list[i], dirname);
if (mkdir(tmp_dir, 0) == SYSERR) {
merr = errno;
continue;
}
/* Now, make the mount-point */
(void) snprintf(mountpt_dir, sizeof (mountpt_dir), "%s/%s",
tmp_dir, dirname);
if (mkdir(mountpt_dir, 0) == SYSERR) {
merr = errno;
continue;
}
lip->li_tmpmp = strdup(mountpt_dir);
lip->li_tmpmp_parent = strdup(tmp_dir);
/* Make sure that the strdup()s both succeeded */
if ((lip->li_tmpmp != NULL) && (lip->li_tmpmp_parent != NULL)) {
rv = RL_SUCCESS;
}
break;
}
/* Get some help if we cannot make the directory. */
if (rv != RL_SUCCESS) {
/*
* If we get a read only filesystem failure (EROFS)
* to make a directory in "/", then we must be fsck'ing
* at boot with a incorrect mnttab.
*
* Just return RL_CORRUPT to indicate it really
* was mounted.
*/
if (merr == EROFS) {
lip->li_mntpoint = strdup("/");
return (RL_CORRUPT);
}
(void) fprintf(stderr, gettext(
"Unable to create temporary "
"directory in any of the directories listed "
"below:\n"));
for (i = 0; i < list_len; i++) {
(void) fprintf(stderr, "\t%s\n", tmp_dir_list[i]);
}
(void) fprintf(stderr, gettext(
"Please correct this problem "
"and rerun the program.\n"));
}
return (rv);
}
/*
* NAME
* rlflush
*
* SYNOPSIS
* rlflush(log_infop)
*
* DESCRIPTION
* Open the mount point of the file system (li_mntpoint) to get a
* file descriptor. Issue the _FIOFFS ioctl to flush the file system
* and then close the device.
*/
static rl_result_t
rlflush(log_info_t *lip)
{
int fd; /* File descriptor. */
rl_result_t rv = RL_SUCCESS;
if ((fd = open((lip->li_mntpoint ? lip->li_mntpoint : lip->li_tmpmp),
O_RDONLY)) == SYSERR) {
return (RL_SYSERR);
}
if (ioctl(fd, _FIOFFS, NULL) == SYSERR) {
rv = RL_SYSERR;
}
(void) close(fd);
return (rv);
}
/*
* NAME
* rlmount
*
* SYNOPSIS
* rlmount(log_infop, mntopt)
*
* DESCRIPTION
* Mount the device specified by li_blkname on li_tmpmp. mntopt specifies
* whether it's mounted RLM_RO or RLM_RW.
*/
static rl_result_t
rlmount(log_info_t *lip, int mntopt)
{
struct ufs_args args;
rl_result_t rv = RL_SUCCESS;
char opt[MAX_MNTOPT_STR];
char *optstr;
int optflg;
args.flags = 0; /* Initialize ufs_args */
/*
* Use a minimal restrictive set of mount options. Make sure
* to use "largefiles" option otherwise mount() can fail w/EFBIG.
* (Although "nosub" isn't a currently supported option on UFS,
* it would be a good one to have if it ever is implemented
* since submounts would prevent a umount.)
*/
args.flags |= UFSMNT_LARGEFILES;
switch (mntopt) {
case RLM_RO:
optstr = MNTOPT_RO;
optflg = MS_RDONLY;
break;
case RLM_RW:
optstr = MNTOPT_RW;
optflg = 0;
break;
default:
return (RL_FAIL);
}
(void) snprintf(opt, sizeof (opt), "%s,%s,%s",
optstr, MNTOPT_NOSUID, MNTOPT_LARGEFILES);
if (mount(lip->li_blkname, lip->li_tmpmp,
optflg | MS_DATA | MS_OPTIONSTR,
MNTTYPE_UFS, &args, sizeof (args),
opt, MAX_MNTOPT_STR) == SYSERR) {
rv = RL_SYSERR;
}
return (rv);
}
/*
* NAME
* rlumount
*
* SYNOPSIS
* rlumount(log_infop)
*
* DESCRIPTION
* Unmounts the device specified by li_blkname, printing an
* error message on failure.
*/
static rl_result_t
rlumount(log_info_t *lip)
{
rl_result_t rv = RL_SUCCESS;
if (umount(lip->li_blkname) == SYSERR) {
(void) fprintf(stderr, gettext(
"WARNING: rlumount(): Can't unmount %s\n"),
lip->li_blkname);
rv = RL_SYSERR;
}
return (rv);
}
/*
* NAME
* rl_log_control
*
* SYNOPSIS
* rl_log_control(block_dev, request)
*
* DESCRIPTION
* Enable/disable logging for the block device "block_dev".
* The request parameter should be set to _FIOLOGENABLE or
* _FIOLOGDISABLE.
*/
rl_result_t
rl_log_control(char *bdev, int request)
{
log_info_t li;
rl_result_t rv = RL_SUCCESS;
rl_result_t alreadymounted;
int fd;
fiolog_t fl;
int logenabled = 0;
if ((request != _FIOLOGENABLE) && (request != _FIOLOGDISABLE))
return (RL_FAIL);
(void) memset((void *)&li, '\0', (size_t)sizeof (li));
if ((alreadymounted = is_mounted(&li, bdev)) != RL_TRUE) {
/*
* Device is not mounted. Need to mount it rw to allow
* the log to be enabled/disabled. To do the mount, we need
* to create a temporary directory, and then remove it when
* we are done.
*/
if (make_mp(&li) != RL_SUCCESS) {
cleanup(&li);
return (RL_FAIL);
}
if (rlmount(&li, RLM_RW) != RL_SUCCESS) {
cleanup(&li);
return (RL_FAIL);
}
}
if (alreadymounted == RL_TRUE)
fd = open(li.li_mntpoint, O_RDONLY);
else
fd = open(li.li_tmpmp, O_RDONLY);
if (fd == SYSERR) {
perror("open");
rv = RL_SYSERR;
goto out;
}
fl.nbytes_requested = 0;
fl.nbytes_actual = 0;
fl.error = FIOLOG_ENONE;
if (ioctl(fd, request, &fl) == SYSERR) {
perror("ioctl");
(void) close(fd);
rv = RL_SYSERR;
goto out;
}
if (ioctl(fd, _FIOISLOG, &logenabled) == SYSERR) {
perror("ioctl");
(void) close(fd);
rv = RL_SYSERR;
goto out;
}
if (((request == _FIOLOGENABLE) && (!logenabled)) ||
((request == _FIOLOGDISABLE) && logenabled))
rv = RL_FAIL;
(void) close(fd);
out:
if (alreadymounted != RL_TRUE)
(void) rlumount(&li);
cleanup(&li);
return (rv);
}