#include "smb_server_prefs.h"
#include "macros.hpp"
#include "lib/common.hpp"
#include "lib/SmbConfig.hpp"
#include "lib/SmbOption.hpp"
#include <cstdlib>
#include <cerrno>
#include <iostream>
#include <memory>
#include <sys/stat.h>
#include <poll.h>
#include <signal.h>
#include <sys/event.h>
#include <fcntl.h>
#ifndef FD_CLOEXEC
#define FD_CLOEXEC 1
#endif
#define DEF_SIGNATURE "Defaults signature"
#define PREF_SIGNATURE "Preferences signature"
#define RULES_SIGNATURE "Configuration rules"
#define LINGER_INTERVAL_NSEC MSEC_TO_NSEC(200)
typedef int (*linger_callback)(void);
#ifdef TESTLEAKS
static void signal_recv(int s) { }
#endif
static void apply_preferences(SmbRules& rules, Preferences& prefs)
{
SmbRules::iterator i;
for (i = rules.begin(); i != rules.end(); ++i) {
(*i)->reset(prefs);
}
}
static void emit_options(SmbRules& rules, SmbConfig& smb)
{
SmbRules::iterator i;
for (i = rules.begin(); i != rules.end(); ++i) {
(*i)->emit(smb);
}
}
static void sync_service(LaunchService& svc, bool stale)
{
bool enabled;
if (Options::ForceSuspend) {
bool ret = launchd_unload_job(svc);
VERBOSE("suspending service %s (%s)\n",
svc.label().c_str(), ret ? "succeeded" : "failed");
return;
}
enabled = svc.enabled();
if (svc.required()) {
if (enabled) {
if (stale || Options::ForceRestart) {
bool ret = launchd_stop_job(svc.label().c_str());
VERBOSE("restarting service %s (%s)\n",
svc.label().c_str(), ret ? "succeeded" : "failed");
return;
}
} else {
bool ret = launchd_load_job(svc);
VERBOSE("enabling service %s (%s)\n",
svc.label().c_str(), ret ? "succeeded" : "failed");
return;
}
} else {
if (enabled) {
bool ret = launchd_unload_job(svc);
VERBOSE("disabling service %s (%s)\n",
svc.label().c_str(), ret ? "succeeded" : "failed");
return;
}
}
VERBOSE("no change to service %s\n", svc.label().c_str());
}
static std::string data_string(CFDataRef data)
{
std::ostringstream ostr;
ostr << data;
return ostr.str();
}
static bool is_config_stale(const SmbConfig& smb,
const char * tag,
const std::string& current)
{
SmbConfig::param_type lastsig;
lastsig.first = tag;
return smb.meta_changed(lastsig);
}
static bool update_preferences(SmbRules& rules, SmbConfig& smb)
{
bool stale = false;
CFDataRef sig;
Preferences defaults(is_server_system() ? kSMBPreferencesServerDefaults
: kSMBPreferencesDesktopDefaults);
Preferences current(kSMBPreferencesAppID);
sig = defaults.create_signature();
smb.set_meta(make_smb_param(DEF_SIGNATURE, sig));
stale |= is_config_stale(smb, DEF_SIGNATURE, data_string(sig));
safe_release(sig);
sig = current.create_signature();
smb.set_meta(make_smb_param(PREF_SIGNATURE, sig));
stale |= is_config_stale(smb, PREF_SIGNATURE, data_string(sig));
safe_release(sig);
smb.set_meta(make_smb_param(RULES_SIGNATURE, rules.version()));
stale |= is_config_stale(smb, RULES_SIGNATURE, rules.version());
VERBOSE("updating preferences from defaults\n");
apply_preferences(rules, defaults);
VERBOSE("updating preferences from current config\n");
apply_preferences(rules, current);
return stale;
}
static int cmd_changes_pending(void)
{
SmbConfig smb;
SmbRules rules;
bool stale;
stale = update_preferences(rules, smb);
VERBOSE("configuration is %s\n", stale ? "out of date" : "current");
return stale ? EXIT_SUCCESS : 2;
}
static int cmd_list_pending(void)
{
SmbConfig smb;
SmbRules rules;
update_preferences(rules, smb);
emit_options(rules, smb);
smb.format(std::cout);
return EXIT_SUCCESS;
}
static int cmd_list_defaults(void)
{
Preferences defaults(is_server_system() ? kSMBPreferencesServerDefaults
: kSMBPreferencesDesktopDefaults);
SmbConfig smb;
SmbRules rules;
CFDataRef sig;
sig = defaults.create_signature();
smb.set_meta(make_smb_param(DEF_SIGNATURE, sig));
smb.set_meta(make_smb_param(RULES_SIGNATURE, rules.version()));
apply_preferences(rules, defaults);
emit_options(rules, smb);
smb.format(std::cout);
safe_release(sig);
return EXIT_SUCCESS;
}
static int cmd_sync_prefs(void)
{
SmbConfig smb;
SmbRules rules;
bool stale;
SyncMutex mutex;
if (getuid() != 0) {
VERBOSE("only root can synchronize SMB preferences\n");
return 1;
}
try {
stale = update_preferences(rules, smb);
emit_options(rules, smb);
if (stale || Options::ForceSync) {
bool success = false;
VERBOSE("rewriting SMB configuration\n");
for (int tries = 5; tries; --tries) {
if (smb.writeback()) {
success = true;
break;
}
}
if (!success) {
VERBOSE("failed to write new SMB configuration\n");
return 1;
}
}
sync_service(smb.SmbdService(), stale);
sync_service(smb.NmbdService(), stale);
sync_service(smb.WinbindService(), stale);
if (smb.SmbdService().enabled() || smb.NmbdService().enabled()) {
post_service_notification("smb", "RUNNING");
} else {
post_service_notification("smb", "STOPPED");
}
} catch (std::exception& e) {
VERBOSE("exception during sync: %s\n", e.what());
return 1;
}
return EXIT_SUCCESS;
}
static struct timespec * timeout_until_ts(long long end_time_usec,
long long now_time_usec,
struct timespec * timeout)
{
unsigned long long timeout_usec = 0;
if (end_time_usec > now_time_usec) {
timeout_usec = end_time_usec - now_time_usec;
}
timeout->tv_sec = USEC_TO_SEC(timeout_usec);
timeout->tv_nsec = 0;
if (timeout->tv_sec == 0 || timeout->tv_sec > 1000) {
timeout->tv_sec = 1;
}
return timeout;
}
#define SMB_PREFS_PARENT "/Library/Preferences/SystemConfiguration"
#define SMB_PREFS_PATH ( SMB_PREFS_PARENT "/" kSMBPreferencesAppID )
#define NEW_CONTENT_VNODE_EVENTS ( NOTE_DELETE | NOTE_WRITE | \
NOTE_EXTEND | NOTE_RENAME | NOTE_REVOKE )
#define FD_STALE_VNODE_EVENTS (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)
static void delete_kevent_watch(int kq, struct kevent *ev)
{
ASSERT(ev->filter == EVFILT_VNODE);
ev->flags = EV_DELETE;
kevent(kq, ev, 1, NULL, 0, NULL);
close(ev->ident);
ev->ident = -1;
}
static int add_kevent_watch(int kq, struct kevent * ev, const char * path)
{
int fd;
int err;
EV_SET(ev, -1 , EVFILT_VNODE, EV_ADD | EV_CLEAR | EV_ENABLE,
NEW_CONTENT_VNODE_EVENTS, 0, NULL);
if ((fd = open(path, O_EVTONLY|O_NOFOLLOW)) == -1) {
return -1;
}
ev->ident = fd;
fcntl(fd, F_SETFD, FD_CLOEXEC);
err = kevent(kq, ev, 1, NULL, 0, NULL);
if (err == -1) {
int errsav = errno;
close(ev->ident);
ev->ident = -1;
errno = errsav;
}
return err;
}
static int wait_for_path_until(int kq,
const char * dirpath,
const char * childpath,
unsigned long long end_time_usec)
{
struct kevent ev;
struct kevent change;
struct timespec timeout;
unsigned long long now_time_usec = time_now_usec();
struct stat sbuf;
int err;
err = add_kevent_watch(kq, &ev, dirpath);
if (err == -1) {
VERBOSE("failed to watch parent %s: %s\n",
dirpath, strerror(errno));
return -1;
}
do {
if (stat(childpath, &sbuf) != -1) {
err = 0;
goto done;
}
zero_struct(change);
err = kevent(kq, NULL, 0, &change, 1,
timeout_until_ts(end_time_usec, now_time_usec, &timeout));
if (err == -1) {
VERBOSE("failed to set up kqueue: %s\n", strerror(errno));
goto done;
}
if (err == 0) {
VERBOSE("timed out watching: %s\n", dirpath);
continue;
}
if (change.fflags & FD_STALE_VNODE_EVENTS) {
err = -1;
goto done;
}
} while ((now_time_usec = time_now_usec()) < end_time_usec);
done:
delete_kevent_watch(kq, &ev);
return err;
}
static int cmd_linger_sync_prefs(linger_callback action,
unsigned idle_usec)
{
std::auto_ptr<Preferences> prefs(new Preferences (SMB_PREFS_PATH));
unsigned long long end_time_usec;
unsigned long long now_time_usec;
CFDataRef last = NULL;
CFDataRef current = NULL;
int kq = -1;
struct kevent ev;
VERBOSE("lingering until %usec idle timeout\n",
USEC_TO_SEC(idle_usec));
zero_struct(ev);
if ((kq = kqueue()) == -1) {
VERBOSE("unable to create a kqueue: %s\n", strerror(errno));
return action();
} else {
fcntl(kq, F_SETFD, FD_CLOEXEC);
if (add_kevent_watch(kq, &ev, SMB_PREFS_PATH) == -1) {
VERBOSE("failed to watch %s: %s\n",
SMB_PREFS_PATH, strerror(errno));
}
}
action();
last = prefs->create_signature();
end_time_usec = time_now_usec() + idle_usec;
while ((now_time_usec = time_now_usec()) < end_time_usec) {
int err = 0;
ASSERT(current == NULL);
if (ev.ident == (uintptr_t)-1) {
if (add_kevent_watch(kq, &ev, SMB_PREFS_PATH) == -1) {
if (errno != ENOENT) {
VERBOSE("failed to watch %s: %s\n",
SMB_PREFS_PATH, strerror(errno));
return 1;
}
if (wait_for_path_until(kq, SMB_PREFS_PARENT, SMB_PREFS_PATH,
end_time_usec) == -1) {
return 1;
}
}
}
if (!prefs->is_loaded()) {
delete prefs.release();
prefs.reset(new Preferences(SMB_PREFS_PATH));
}
current = prefs->create_signature();
if (ev.ident != (uintptr_t)-1 &&
cftype_equal<CFDataRef>(last, current)) {
struct kevent change = {0};
struct timespec timeout_ts;
timeout_until_ts(end_time_usec, now_time_usec, &timeout_ts);
VERBOSE("waiting for preferences update (%lusec timeout)\n",
(unsigned long)timeout_ts.tv_sec);
err = kevent(kq, NULL, 0, &change, 1, &timeout_ts);
if (err == -1) {
VERBOSE("failed to watch kqueue: %s\n", strerror(errno));
delete_kevent_watch(kq, &ev);
} else if (change.fflags & FD_STALE_VNODE_EVENTS) {
VERBOSE("kevent fd %d gone stale (fflags=%#x), "
"deleting kevent watch\n",
(int)ev.ident, ev.fflags);
delete_kevent_watch(kq, &ev);
}
safe_release(current);
current = prefs->create_signature();
}
if (cftype_equal<CFDataRef>(last, current)) {
VERBOSE("preferences signatures match, spurious linger wakeup\n");
} else {
#if DEBUG
std::string last_str(data_string(last));
std::string current_str(data_string(current));
ASSERT(last_str != current_str);
DEBUGMSG("last = %s\n", last_str.c_str());
DEBUGMSG("current = %s\n", current_str.c_str());
#endif
action();
safe_release(last);
last = current;
safe_retain(last);
VERBOSE("pushing idle timeout %lusec forward\n",
(unsigned long)USEC_TO_SEC(idle_usec));
end_time_usec = time_now_usec() + idle_usec;
}
safe_release(current);
if (err == -1) {
return 1;
}
}
safe_release(last);
safe_release(current);
return EXIT_SUCCESS;
}
int main(int argc, char * const * argv)
{
unsigned idle_sec = 30;
linger_callback action;
setprogname("synchronize-preferences");
umask(0);
Options::parse(argc, argv);
#if DEBUG
Options::Debug = true;
std::cout << "WARNING: debug mode emabled\n";
#endif
if (Options::Linger) {
if (!launchd_checkin(&idle_sec)) {
VERBOSE("launchd checking failed: %s\n",
strerror(errno));
} else {
VERBOSE("lingering for %u secs\n",
idle_sec);
}
}
#ifdef TESTLEAKS
signal(SIGHUP, signal_recv);
while (1) {
cmd_changes_pending();
cmd_list_pending();
cmd_list_defaults();
cmd_sync_prefs();
printf("done\n");
poll(NULL, 0, -1);
}
return EXIT_SUCCESS;
#endif
switch(Options::Command) {
case Options::CHANGES_PENDING:
action = cmd_changes_pending;
break;
case Options::LIST_PENDING:
action = cmd_list_pending;
break;
case Options::LIST_DEFAULTS:
action = cmd_list_defaults;
break;
default:
ASSERT(Options::Command == Options::SYNC);
action = cmd_sync_prefs;
}
return Options::Linger ?
cmd_linger_sync_prefs(action, SEC_TO_USEC(idle_sec))
: action();
}