#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <dirent.h>
#include <signal.h>
#include <errno.h>
#include <libintl.h>
#include <zone.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dkio.h>
#include <sys/cdio.h>
#include <sys/vtoc.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <libcontract.h>
#include <sys/contract/process.h>
#include <sys/ctfs.h>
#include <tsol/label.h>
#include "vold.h"
#include "rmm_common.h"
int rmm_debug = 0;
boolean_t rmm_vold_actions_enabled = B_FALSE;
boolean_t rmm_vold_mountpoints_enabled = B_FALSE;
static char *prog_name = NULL;
static pid_t prog_pid = 0;
static int system_labeled = 0;
static uid_t mnt_uid = (uid_t)-1;
static gid_t mnt_gid = (gid_t)-1;
static zoneid_t mnt_zoneid = -1;
static char mnt_zoneroot[MAXPATHLEN];
static char mnt_userdir[MAXPATHLEN];
static const char notify_characters[] = {
'e',
'i',
'r',
'u'
};
static const char *result_strings[] = {
"FALSE",
"TRUE"
};
#define NOTIFY_DIR "/tmp/.removable"
#define NOTIFY_NAME "notify"
static void volrmmount_usage();
static void volcheck_usage();
static int vold_action(struct action_arg *aap);
static void vold_update_mountpoints(struct action_arg *aap);
static char *not_mountable(struct action_arg *aa);
static int create_one_notify_file(char *fstype,
char *mount_point,
char *notify_file,
char *raw_partitionp,
char *reason,
char *symdev);
static int create_notify_files(struct action_arg **aa);
static boolean_t notify_clients(action_t action, int do_notify);
static void popdir(int fd);
static int pushdir(const char *dir);
static boolean_t remove_notify_files(struct action_arg **aa);
void
vold_init(int argc, char **argv)
{
system_labeled = is_system_labeled();
}
int
vold_rmmount(int argc, char **argv)
{
char *volume_action;
char *volume_mediatype;
char *volume_mount_mode;
char *volume_name;
char *volume_path;
char *volume_pcfs_id;
char *volume_symdev;
char *volume_zonename;
char *volume_user;
action_t action;
char mountpoint[MAXPATHLEN];
char *zonemountpoint;
char *arg_mountpoint = NULL;
LibHalContext *hal_ctx;
DBusError error;
rmm_error_t rmm_error;
int ret;
prog_name = argv[0];
prog_pid = getpid();
mnt_zoneroot[0] = '\0';
mnt_userdir[0] = '\0';
volume_action = getenv("VOLUME_ACTION");
volume_mediatype = getenv("VOLUME_MEDIATYPE");
volume_mount_mode = getenv("VOLUME_MOUNT_MODE");
volume_name = getenv("VOLUME_NAME");
volume_path = getenv("VOLUME_PATH");
volume_pcfs_id = getenv("VOLUME_PCFS_ID");
volume_symdev = getenv("VOLUME_SYMDEV");
if (system_labeled) {
volume_zonename = getenv("VOLUME_ZONE_NAME");
volume_user = getenv("VOLUME_USER");
}
if (volume_action == NULL) {
dbgprintf("%s(%ld): VOLUME_ACTION was null!!\n",
prog_name, prog_pid);
return (-1);
}
if (volume_mediatype == NULL) {
dbgprintf("%s(%ld): VOLUME_MEDIATYPE was null!!\n",
prog_name, prog_pid);
return (-1);
}
if (volume_mount_mode == NULL) {
volume_mount_mode = "rw";
}
if (volume_name == NULL) {
dbgprintf("%s(%ld): VOLUME_NAME was null!!\n",
prog_name, prog_pid);
return (-1);
}
if (volume_path == NULL) {
dbgprintf("%s(%ld): VOLUME_PATH was null!!\n",
prog_name, prog_pid);
return (-1);
}
if (volume_pcfs_id == NULL) {
volume_pcfs_id = "";
}
if (volume_symdev == NULL) {
dbgprintf("%s(%ld): VOLUME_SYMDEV was null!!\n",
prog_name, prog_pid);
return (-1);
}
if (system_labeled) {
if (volume_zonename != NULL &&
strcmp(volume_zonename, GLOBAL_ZONENAME) != 0) {
if ((mnt_zoneid =
getzoneidbyname(volume_zonename)) != -1) {
if (zone_getattr(mnt_zoneid, ZONE_ATTR_ROOT,
mnt_zoneroot, MAXPATHLEN) == -1) {
dbgprintf("%s(%ld): NO ZONEPATH!!\n",
prog_name, prog_pid);
return (-1);
}
}
} else {
mnt_zoneid = GLOBAL_ZONEID;
mnt_zoneroot[0] = '\0';
}
if (volume_user != NULL) {
struct passwd *pw;
if ((pw = getpwnam(volume_user)) == NULL) {
dbgprintf("%s(%ld) %s\n", prog_name, prog_pid,
": VOLUME_USER was not a valid user!");
return (-1);
}
mnt_uid = pw->pw_uid;
mnt_gid = pw->pw_gid;
if (snprintf(mnt_userdir, sizeof (mnt_userdir),
"/%s-%s", volume_user, volume_symdev) >=
sizeof (mnt_userdir))
return (-1);
} else {
mnt_uid = 0;
mnt_userdir[0] = '\0';
}
rmm_vold_mountpoints_enabled = B_FALSE;
rmm_vold_actions_enabled = B_TRUE;
} else {
rmm_vold_mountpoints_enabled = B_TRUE;
rmm_vold_actions_enabled = B_TRUE;
}
if ((hal_ctx = rmm_hal_init(0, 0, 0, 0, &error, &rmm_error)) == NULL) {
rmm_dbus_error_free(&error);
if (geteuid() != 0) {
(void) fprintf(stderr,
gettext("%s(%ld) error: must be root to execute\n"),
prog_name, prog_pid);
return (-1);
}
}
if (strcmp(volume_action, "eject") == 0) {
action = EJECT;
} else if (strcmp(volume_action, "insert") == 0) {
action = INSERT;
if (system_labeled) {
if (strlen(mnt_userdir) > 0) {
if (snprintf(mountpoint, MAXPATHLEN,
"%s/%s%s", mnt_zoneroot, volume_mediatype,
mnt_userdir) > MAXPATHLEN) {
return (-1);
}
(void) makepath(mountpoint, 0700);
(void) chown(mountpoint, mnt_uid, mnt_gid);
if (snprintf(mountpoint, MAXPATHLEN,
"%s/%s", mnt_zoneroot,
volume_mediatype) <= MAXPATHLEN) {
(void) chmod(mountpoint, 0755);
}
}
if (snprintf(mountpoint, MAXPATHLEN,
"%s/%s%s/%s", mnt_zoneroot, volume_mediatype,
mnt_userdir, volume_name) > MAXPATHLEN) {
(void) fprintf(stderr,
gettext("%s(%ld) error: path too long\n"),
prog_name, prog_pid);
return (-1);
}
(void) makepath(mountpoint, 0755);
arg_mountpoint = mountpoint;
}
} else if (strcmp(volume_action, "remount") == 0) {
action = REMOUNT;
} else if (strcmp(volume_action, "unmount") == 0) {
action = UNMOUNT;
}
ret = rmm_action(hal_ctx, volume_symdev, action, 0, 0, 0,
arg_mountpoint) ? 0 : 1;
if (hal_ctx != NULL) {
rmm_hal_fini(hal_ctx);
}
return (ret);
}
int
vold_postprocess(LibHalContext *hal_ctx, const char *udi,
struct action_arg *aap)
{
int ret = 0;
if ((aap->aa_action == INSERT) || (aap->aa_action == REMOUNT)) {
rmm_volume_aa_update_mountpoint(hal_ctx, udi, aap);
if ((aap->aa_mountpoint == NULL) ||
(strlen(aap->aa_mountpoint) == 0)) {
return (1);
}
}
if (rmm_vold_mountpoints_enabled) {
vold_update_mountpoints(aap);
}
if (rmm_vold_actions_enabled) {
ret = vold_action(aap);
}
return (ret);
}
static void
vold_update_mountpoints(struct action_arg *aap)
{
boolean_t is_partition;
char part_dir[2 * MAXNAMELEN];
char symname_mp[2 * MAXNAMELEN];
char symcontents_mp[MAXNAMELEN];
char symname[2 * MAXNAMELEN];
char symcontents[MAXNAMELEN];
is_partition = (aap->aa_partname != NULL);
if (!system_labeled) {
if (!is_partition) {
(void) snprintf(symcontents_mp, sizeof (symcontents_mp),
"%s", aap->aa_mountpoint);
(void) snprintf(symname_mp, sizeof (symname_mp),
"/%s/%s", aap->aa_media, aap->aa_name);
} else {
(void) snprintf(part_dir, sizeof (part_dir),
"/%s/%s", aap->aa_media, aap->aa_name);
(void) snprintf(symcontents_mp, sizeof (symcontents_mp),
"%s", aap->aa_mountpoint);
(void) snprintf(symname_mp, sizeof (symname_mp),
"/%s/%s/%s", aap->aa_media, aap->aa_name,
aap->aa_partname);
}
(void) snprintf(symcontents, sizeof (symcontents),
"./%s", aap->aa_name);
(void) snprintf(symname, sizeof (symname),
"/%s/%s", aap->aa_media, aap->aa_symdev);
} else {
if (!is_partition) {
(void) snprintf(symcontents_mp, sizeof (symcontents_mp),
"%s", aap->aa_mountpoint);
(void) snprintf(symname_mp, sizeof (symname_mp),
"%s/%s/%s", mnt_zoneroot, aap->aa_media,
aap->aa_symdev);
} else {
(void) snprintf(symcontents_mp, sizeof (symcontents_mp),
"%s", aap->aa_mountpoint);
(void) snprintf(symname_mp, sizeof (symname_mp),
"%s/%s/%s", mnt_zoneroot, aap->aa_media,
aap->aa_symdev, aap->aa_partname);
}
(void) snprintf(symcontents, sizeof (symcontents),
".%s/%s", mnt_userdir, aap->aa_name);
(void) snprintf(symname, sizeof (symname), "%s/%s/%s",
mnt_zoneroot, aap->aa_media, aap->aa_symdev);
}
(void) unlink(symname);
(void) unlink(symname_mp);
if (is_partition) {
(void) rmdir(part_dir);
}
if ((aap->aa_action == INSERT) || (aap->aa_action == REMOUNT)) {
(void) mkdir(aap->aa_media, 0755);
if (is_partition) {
(void) mkdir(part_dir, 0755);
}
(void) symlink(symcontents_mp, symname_mp);
(void) symlink(symcontents, symname);
}
}
static int
vold_action(struct action_arg *aap)
{
action_t action;
int result;
int do_notify = FALSE;
action_t notify_act = EJECT;
struct action_arg *aa[2];
struct action_arg a1;
dbgprintf("%s[%d]: entering action()\n", __FILE__, __LINE__);
if (mnt_zoneid > GLOBAL_ZONEID) {
pid_t pid;
int status;
int ifx;
int tmpl_fd;
int err = 0;
tmpl_fd = open64(CTFS_ROOT "/process/template",
O_RDWR);
if (tmpl_fd == -1)
return (1);
err |= ct_tmpl_set_critical(tmpl_fd, 0);
err |= ct_tmpl_set_informative(tmpl_fd, 0);
err |= ct_pr_tmpl_set_fatal(tmpl_fd,
CT_PR_EV_HWERR);
err |= ct_pr_tmpl_set_param(tmpl_fd,
CT_PR_PGRPONLY |
CT_PR_REGENT);
if (err || ct_tmpl_activate(tmpl_fd)) {
(void) close(tmpl_fd);
return (1);
}
switch (pid = fork1()) {
case 0:
(void) ct_tmpl_clear(tmpl_fd);
for (ifx = 0; ifx < _NFILE; ifx++)
(void) close(ifx);
if (zone_enter(mnt_zoneid) == -1)
_exit(0);
break;
case -1:
dbgprintf("fork1 failed \n ");
return (1);
default :
(void) ct_tmpl_clear(tmpl_fd);
(void) close(tmpl_fd);
if (waitpid(pid, &status, 0) < 0) {
dbgprintf("%s(%ld): waitpid() "
"failed (errno %d) \n",
prog_name, prog_pid, errno);
return (1);
}
}
}
a1.aa_path = NULL;
aa[0] = aap;
aa[1] = &a1;
action = aa[0]->aa_action;
if (action == CLEAR_MOUNTS) {
result = remove_notify_files(aa);
result = TRUE;
} else if (action == EJECT) {
result = remove_notify_files(aa);
if (result == TRUE) {
do_notify = TRUE;
notify_act = EJECT;
}
} else if (action = INSERT) {
result = create_notify_files(aa);
if (result == TRUE) {
do_notify = TRUE;
notify_act = INSERT;
}
} else if (action == REMOUNT) {
result = create_notify_files(aa);
if (result == TRUE) {
do_notify = TRUE;
notify_act = REMOUNT;
}
} else if (action == UNMOUNT) {
result = remove_notify_files(aa);
if (result == TRUE) {
do_notify = TRUE;
notify_act = UNMOUNT;
}
} else {
dbgprintf("%s[%d]: action(): invalid action: %s\n",
__FILE__, __LINE__, action);
result = FALSE;
}
if (result == TRUE) {
result = notify_clients(notify_act, do_notify);
}
dbgprintf("%s[%d]: leaving action(), result = %s\n",
__FILE__, __LINE__, result_strings[result]);
if (mnt_zoneid > GLOBAL_ZONEID) {
_exit(0);
}
if (result == TRUE) {
return (0);
} else {
return (1);
}
}
static char *
not_mountable(struct action_arg *aa)
{
return (NULL);
}
static int
create_notify_files(struct action_arg **aa)
{
int ai;
char *fstype;
char *mount_point;
char notify_file[64];
char *raw_partitionp;
char *reason;
int result;
char *symdev;
dbgprintf("%s[%d]: entering create_notify_files()\n",
__FILE__, __LINE__);
ai = 0;
result = FALSE;
symdev = aa[ai]->aa_symdev;
while ((aa[ai] != NULL) && (aa[ai]->aa_path != NULL)) {
if (aa[ai]->aa_mountpoint != NULL) {
if (aa[ai]->aa_type) {
fstype = aa[ai]->aa_type;
} else {
fstype = "unknown";
}
mount_point = aa[ai]->aa_mountpoint;
if (aa[ai]->aa_partname != NULL) {
sprintf(notify_file, "%s-%s", symdev,
aa[ai]->aa_partname);
} else {
sprintf(notify_file, "%s-0", symdev);
}
reason = NULL;
} else {
fstype = "none";
mount_point = "none";
reason = not_mountable(aa[ai]);
if (reason != NULL) {
sprintf(notify_file, "%s-0", symdev);
} else {
if (aa[0]->aa_partname != NULL) {
sprintf(notify_file, "%s-%s", symdev,
aa[0]->aa_partname);
} else {
sprintf(notify_file, "%s-0", symdev);
}
if ((aa[0]->aa_type != NULL) &&
(strcmp(aa[0]->aa_type, "backup_slice")
== 0)) {
reason = "backup_slice";
} else {
reason = "unformatted_media";
}
}
}
raw_partitionp = aa[0]->aa_rawpath;
result = create_one_notify_file(fstype,
mount_point,
notify_file,
raw_partitionp,
reason,
symdev);
ai++;
}
dbgprintf("%s[%d]: leaving create_notify_files(), result = %s\n",
__FILE__, __LINE__, result_strings[result]);
return (result);
}
static int
create_one_notify_file(char *fstype,
char *mount_point,
char *notify_file,
char *raw_partitionp,
char *reason,
char *symdev)
{
int current_working_dir_fd;
int file_descriptor;
FILE *filep;
int result;
dbgprintf("%s[%d]:Entering create_one_notify_file()\n",
__FILE__, __LINE__);
dbgprintf("\tcreate_one_notify_file(): fstype = %s\n", fstype);
dbgprintf("\tcreate_one_notify_file(): mount_point = %s\n",
mount_point);
dbgprintf("\tcreate_one_notify_file(): notify_file = %s\n",
notify_file);
dbgprintf("\tcreate_one_notify_file(): raw_partitionp = %s\n",
raw_partitionp);
if (reason != NULL) {
dbgprintf("\tcreate_one_notify_file(): reason = %s\n", reason);
} else {
dbgprintf("\tcreate_one_notify_file(): reason = NULL\n");
}
dbgprintf("\tcreate_one_notify_file(): symdev = %s\n", symdev);
result = TRUE;
current_working_dir_fd = pushdir(NOTIFY_DIR);
if (current_working_dir_fd < 0) {
(void) makepath(NOTIFY_DIR, 0777);
current_working_dir_fd = pushdir(NOTIFY_DIR);
if (current_working_dir_fd < 0) {
result = FALSE;
}
}
if (result == TRUE) {
(void) remove(notify_file);
file_descriptor =
open(notify_file, O_CREAT|O_EXCL|O_WRONLY, 0644);
if (file_descriptor < 0) {
dbgprintf("%s[%d]: can't create %s/%s; %m\n",
__FILE__, __LINE__, NOTIFY_DIR, notify_file);
result = FALSE;
} else {
filep = fdopen(file_descriptor, "w");
if (filep != NULL) {
if (reason == NULL) {
(void) fprintf(filep, "%s %s %s",
mount_point,
raw_partitionp,
fstype);
(void) fclose(filep);
dbgprintf("%s[%d]: Just wrote %s %s %s to %s\n",
__FILE__,
__LINE__,
mount_point,
raw_partitionp,
fstype,
notify_file);
} else {
(void) fprintf(filep, "%s %s",
reason, raw_partitionp);
(void) fclose(filep);
dbgprintf("%s[%d]: Just wrote %s %s to %s\n",
__FILE__,
__LINE__,
reason,
raw_partitionp,
notify_file);
}
} else {
dbgprintf("%s[%d]: can't write %s/%s; %m\n",
__FILE__, __LINE__,
NOTIFY_DIR, notify_file);
(void) close(file_descriptor);
result = FALSE;
}
}
popdir(current_working_dir_fd);
}
dbgprintf("%s[%d]: leaving create_one_notify_file, result = %s\n",
__FILE__, __LINE__, result_strings[result]);
return (result);
}
static boolean_t
notify_clients(action_t action, int do_notify)
{
int current_working_dir_fd;
DIR *dirp;
struct dirent *dir_entryp;
size_t len;
int fd;
char namebuf[MAXPATHLEN];
char notify_character;
void (*old_signal_handler)();
int result;
struct stat sb;
dbgprintf("%s[%d]: entering notify_clients()\n", __FILE__, __LINE__);
result = TRUE;
if ((current_working_dir_fd = pushdir(NOTIFY_DIR)) < 0) {
result = FALSE;
}
if (result == TRUE) {
dirp = opendir(".");
if (dirp == NULL) {
dbgprintf("%s[%d]:opendir failed on '.'; %m\n",
__FILE__, __LINE__);
popdir(current_working_dir_fd);
result = FALSE;
}
}
if (result == TRUE) {
result = FALSE;
old_signal_handler = signal(SIGPIPE, SIG_IGN);
len = strlen(NOTIFY_NAME);
while (dir_entryp = readdir(dirp)) {
if (strncmp(dir_entryp->d_name, NOTIFY_NAME, len)
!= 0) {
continue;
}
result = TRUE;
if (do_notify != TRUE) {
continue;
}
(void) sprintf(namebuf, "%s/%s",
NOTIFY_DIR, dir_entryp->d_name);
if ((fd = open(namebuf, O_WRONLY|O_NDELAY)) < 0) {
dbgprintf("%s[%d]: open failed for %s; %m\n",
__FILE__, __LINE__, namebuf);
continue;
}
if ((fstat(fd, &sb) < 0) || (!S_ISFIFO(sb.st_mode))) {
dbgprintf("%s[%d]: %s isn't a named pipe\n",
__FILE__, __LINE__, namebuf);
(void) close(fd);
continue;
}
notify_character = notify_characters[action];
if (write(fd, ¬ify_character, 1) < 0) {
dbgprintf("%s[%d]: write failed for %s; %m\n",
__FILE__, __LINE__, namebuf);
(void) close(fd);
continue;
}
(void) close(fd);
}
(void) closedir(dirp);
(void) signal(SIGPIPE, old_signal_handler);
popdir(current_working_dir_fd);
}
dbgprintf("%s[%d]: leaving notify_clients(), result = %s\n",
__FILE__, __LINE__, result_strings[result]);
return (result);
}
static void
popdir(int fd)
{
if (fchdir(fd) < 0) {
dbgprintf("%s[%d]: popdir() failed\n", __FILE__, __LINE__);
exit(1);
}
(void) close(fd);
}
static int
pushdir(const char *dir)
{
int current_working_dir_fd;
struct stat stat_buf;
if (lstat(dir, &stat_buf) < 0) {
dbgprintf("%s[%d]: push_dir_and_check(): %s does not exist\n",
__FILE__, __LINE__, dir);
return (-1);
}
if (!(S_ISDIR(stat_buf.st_mode))) {
dbgprintf("%s[%d]: push_dir_and_check(): %s not a directory.\n",
__FILE__, __LINE__, dir);
(void) remove(dir);
return (-1);
}
if ((current_working_dir_fd = open(".", O_RDONLY)) < 0) {
dbgprintf("%s[%d]: push_dir_and_check(): can't open %s.\n",
__FILE__, __LINE__, dir);
return (-1);
}
if (chdir(dir) < 0) {
(void) close(current_working_dir_fd);
dbgprintf("%s[%d]: push_dir_and_check(): "
"can't chdir() to %s.\n", __FILE__, __LINE__, dir);
return (-1);
}
return (current_working_dir_fd);
}
static boolean_t
remove_notify_files(struct action_arg **aa)
{
int ai;
int current_working_dir_fd;
char notify_file[64];
int result;
char *symdev;
dbgprintf("%s[%d]: entering remove_notify_files()\n",
__FILE__, __LINE__);
ai = 0;
result = TRUE;
symdev = aa[ai]->aa_symdev;
while ((result == TRUE) &&
(aa[ai] != NULL) &&
(aa[ai]->aa_path != NULL)) {
if (not_mountable(aa[ai])) {
sprintf(notify_file, "%s-0", symdev);
} else if (aa[ai]->aa_partname != NULL) {
sprintf(notify_file, "%s-%s",
symdev, aa[0]->aa_partname);
} else {
sprintf(notify_file, "%s-0", symdev);
}
current_working_dir_fd = pushdir(NOTIFY_DIR);
if (current_working_dir_fd < 0) {
result = FALSE;
}
if ((result == TRUE) && (remove(notify_file) < 0)) {
dbgprintf("%s[%d]: remove %s/%s; %m\n",
__FILE__, __LINE__, NOTIFY_DIR, notify_file);
result = FALSE;
}
if (current_working_dir_fd != -1) {
popdir(current_working_dir_fd);
}
ai++;
}
dbgprintf("%s[%d]: leaving remove_notify_files(), result = %s\n",
__FILE__, __LINE__, result_strings[result]);
return (result);
}