synchronize-preferences [plain text]
ENV.clear
ENV['__CF_USER_TEXT_ENCODING'] = "0x#{Process::Sys::getuid()}:0:0"
require 'osx/foundation'
require 'optparse'
require 'open3'
require 'ipaddr'
$VERBOSE = false $LINGER = 0 $RESTART = false $DEBUG = false
$ID = '$Id: synchronize-preferences 32597 2007-07-22 20:51:18Z jpeach $'
mutex = File.open($0, 'r')
mutex.flock(File::LOCK_EX)
$0 = File.basename($0)
OSX.require_framework 'SystemConfiguration'
class Preferences
def initialize(appid)
appid = "#{appid}.plist" unless appid =~ /\.plist$/
@prefs = OSX::SCPreferencesCreate(nil, $0, appid)
@keys = Preferences.to_native(OSX::SCPreferencesCopyKeyList(@prefs))
@keys.push('PreferencesSignature')
$stderr.print \
"SCPreferences (appid=#{appid}) keys: #{@keys.join(",")}\n" \
if $DEBUG
end
def each
keys.each { | key |
yield key, self[key]
}
end
def signature
sig = OSX::SCPreferencesGetSignature(@prefs)
return sig.to_s.gsub(/[ <>]/, '')
end
def has_key?(key)
return @keys.include?(key)
end
def [](key)
case key
when 'PreferencesSignature'
return self.signature
else
val = OSX::SCPreferencesGetValue(@prefs, key)
return (Preferences.to_native(val) rescue nil)
end
end
def Preferences.load_plist(path)
print "#{$0}: loading #{path}\n" if $VERBOSE
data = OSX::NSData.dataWithContentsOfFile(path)
return nil unless data
plist, format, err = OSX::NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription(data,
OSX::NSPropertyListImmutable)
if (plist == nil or !plist.kind_of? OSX::NSCFDictionary)
return nil
end
return Preferences.to_native(plist)
end
def Preferences.to_native(val)
return nil if val == nil
$stderr.print "converting (#{val.class})\n" if $DEBUG
if val.kind_of? OSX::NSCFBoolean
return (val == OSX::KCFBooleanTrue ? true : false)
end
if val.kind_of? OSX::NSCFString
return val.to_s
end
if val.kind_of? OSX::NSCFNumber
return val.to_i
end
if val.kind_of? OSX::NSCFArray
array = []
val.each { |element| array += [ Preferences.to_native(element) ] }
return array
end
if val.kind_of? OSX::NSCFDictionary
hash = {}
val.allKeys().each { | key |
new_key = Preferences.to_native(key)
new_val = Preferences.to_native(val[key])
hash[new_key] = new_val
}
return hash
end
$stderr.print \
"#{$0}: preferences type #{val.class} is not supported\n" \
if $VERBOSE
return nil
end
end
class ShellCommand
LAUNCHCTL = '/bin/launchctl'
SW_VERS = '/usr/bin/sw_vers'
def ShellCommand.run(*cmd)
return false unless cmd.length > 1
return false unless (cmd[0] == LAUNCHCTL || cmd[0] == SW_VERS)
print "#{$0}: running command: '#{cmd.join("' '")}'\n" if $VERBOSE;
io = Open3.popen3(*cmd) { | stdin, stdout, stderr |
stdin.close
loop do
read_array = [stdout, stderr].reject { |fd| fd.eof? }
break if read_array.empty?
ready = Kernel.select(read_array, nil, nil, 0.1)
next if ready == nil
ready.flatten.each { | fd |
line = (fd.readline rescue nil)
next if line == nil
if (fd == stderr)
$stderr.print "#{$0} (#{cmd[0]}): #{line}" \
if $VERBOSE
else
if block_given?
yield line
else
$stdout.print line \
if $VERBOSE
end
end
}
end
}
end
end
class NotificationCenter
def initialize
@center = OSX::NSDistributedNotificationCenter.defaultCenter()
end
def post(notification, info)
@center.postNotificationName_object_userInfo_options(
notification, nil, info, OSX::NSNotificationPostToAllSessions);
end
end
class LaunchControl
SMBD = {
'service' => 'org.samba.smbd',
'plist' => '/System/Library/LaunchDaemons/smbd.plist',
'enabled' => false,
'required' => false
}
WINBINDD = {
'service' => 'org.samba.winbindd',
'plist' => '/System/Library/LaunchDaemons/org.samba.winbindd.plist',
'enabled' => false,
'required' => true }
NMBD = {
'service' => 'org.samba.nmbd',
'plist' => '/System/Library/LaunchDaemons/nmbd.plist',
'enabled' => false,
'required' => false
}
@@force_restart = false @@force_disable = false @@config_change = true
def LaunchControl.force_restart
@@force_restart = true
end
def LaunchControl.force_disable
@@force_disable = true
end
def LaunchControl.no_config_change
@@config_change = false
end
def LaunchControl.init
ShellCommand.run(ShellCommand::LAUNCHCTL, 'list') { |line|
fields = line.split(/\s+/)
next if fields.length != 3
case fields[2]
when SMBD['service']
SMBD['enabled'] = true
when WINBINDD['service']
WINBINDD['enabled'] = true
when NMBD['service']
NMBD['enabled'] = true
end
}
end
def LaunchControl.require(service)
print "#{$0}: requiring service #{service['service']}\n" \
if $VERBOSE
case service
when SMBD
SMBD['required'] = true
when WINBINDD
WINBINDD['required'] = true
when NMBD
NMBD['required'] = true
end
end
def LaunchControl.sync
print "#{$0}: syncing, config_change=#{@@config_change}, " +
"force_restart=#{@@force_restart}, " +
"force_disable=#{@@force_disable}\n" \
if $VERBOSE
[SMBD, WINBINDD, NMBD].each { |service|
if $VERBOSE
print "#{$0}: service #{service['service']} "
print "required=#{service['required']} "
print "enabled=#{service['enabled']}\n"
end
if @@force_disable
launchd_disable(service['plist'])
next
end
if service['required']
if service['enabled']
if @@config_change || @@force_restart
print "#{$0}: restarting #{service['service']}\n" \
if $VERBOSE
launchd_stop(service['service'])
end
else
launchd_enable(service['plist'])
end
else
if service['enabled']
launchd_disable(service['plist'])
end
end
}
notify = NotificationCenter.new()
notify.post('com.apple.ServiceConfigurationChangedNotification',
{
'ServiceName' => 'smb',
'State' => (SMBD['enabled'] ? 'RUNNING' : 'STOPPED')
})
end
private
def LaunchControl.launchd_stop(service)
ShellCommand.run(ShellCommand::LAUNCHCTL, 'stop', service)
end
def LaunchControl.launchd_enable(plist)
ShellCommand.run(ShellCommand::LAUNCHCTL, 'load', '-w', plist)
end
def LaunchControl.launchd_disable(plist)
ShellCommand.run(ShellCommand::LAUNCHCTL, 'unload', '-w', plist)
end
end
class NullOption
def initialize(name)
@name = name
end
def emit(prefs, config)
print "#{$0}: ignoring unimplemented option '#{@name}'\n" if $VERBOSE
end
def to_s
return @name
end
end
class SuspendOption
def initialize(name)
@name = name
end
def emit(prefs, config)
if prefs[@name]
LaunchControl.force_disable
end
end
end
class SimpleOption < NullOption
def initialize(name, text)
@name = name
@text = text
end
def emit(prefs, config)
return unless self.has_value?(prefs)
if (prefs[@name].class == TrueClass || prefs[@name].class == FalseClass)
val = prefs[@name] ? 'yes' : 'no'
elsif prefs[@name].class == String
val = prefs[@name]
elsif prefs[@name].class == Fixnum
val = "#{prefs[@name]}"
else
$stderr.print \
"#{$0}: the #{@name} preference is an invalid type (#{prefs[@name].class})\n" \
if $VERBOSE
return
end
config.append(@text, val)
end
def has_value?(prefs)
val = prefs[@name]
return false if val == nil
return false if val.kind_of?(String) && val == ""
return false if val.kind_of?(Array) && val.length == 0
return true
end
end
class KerberosRealmOption < NullOption
def initialize(realm_tag, local_realm_tag)
@name = realm_tag
@mrealm_tag = realm_tag
@lrealm_tag = local_realm_tag
end
def emit(prefs, config)
mrealm = SimpleOption.new(@mrealm_tag, 'realm')
lrealm = SimpleOption.new(@lrealm_tag, 'com.apple: lkdc realm')
return unless (mrealm.has_value?(prefs) || lrealm.has_value?(prefs))
mrealm.emit(prefs, config)
lrealm.emit(prefs, config)
config.append('use kerberos keytab', 'yes')
if lrealm.has_value?(prefs) && !mrealm.has_value?(prefs)
SimpleOption.new(@lrealm_tag, 'realm').emit(prefs, config)
end
end
end
class WinsRegisterOption < NullOption
def initialize(register_tag, addrlist_tag)
@name = register_tag
@addrlist = addrlist_tag
end
def emit(prefs, config)
return unless prefs[@name]
list = IPAddressOption.new(@addrlist, 'wins server')
list.emit(prefs, config)
LaunchControl.require(LaunchControl::NMBD)
end
end
class GuestAccessOption < NullOption
def initialize(pref)
@name = pref
end
def emit(prefs, config)
if self.enabled(prefs)
config.append('map to guest', 'Bad User')
else
config.append('map to guest', 'Never')
end
config.append_section(SmbConfigFile::STATUS,
'Guest access', self.enabled(prefs) ? 'per-share' : 'never')
end
def enabled(prefs)
return prefs[@name] ? true : false
end
end
class ServicesOption < NullOption
WINS = 'wins'
DISK = 'disk'
PRINT = 'print'
def initialize(pref)
@name = pref
end
def emit(prefs, config)
disk = false
print = false
wins = false
prefs[@name] = [] unless prefs[@name] != nil
begin
prefs[@name].each { |service|
case
when service.casecmp(WINS) == 0
wins = true
LaunchControl.require(LaunchControl::NMBD)
when service.casecmp(DISK) == 0
disk = true
LaunchControl.require(LaunchControl::NMBD)
LaunchControl.require(LaunchControl::SMBD)
when service.casecmp(PRINT) == 0
print = true
LaunchControl.require(LaunchControl::NMBD)
LaunchControl.require(LaunchControl::SMBD)
end
}
rescue StandardError => err
if $VERBOSE
$stderr.print "#{$0}: #{err}\n"
$stderr.print \
"#{$0}: the #{@name} preference is missing or invalid\n"
end
end
config.append('wins support', wins ? 'yes' : 'no')
config.append('enable disk services', disk ? 'yes' : 'no')
config.append('enable print services', print ? 'yes' : 'no')
end
end
class AutoSharesOption < NullOption
def initialize(homes_tag, admin_tag)
super("#{homes_tag} or #{admin_tag}")
@homes = homes_tag
@admin = admin_tag
end
def emit(prefs, config)
need_homes = (prefs[@homes] || prefs[@admin])
if need_homes
config.append_section(SmbConfigFile::HOMES,
'comment', 'User Home Directories')
config.append_section(SmbConfigFile::HOMES,
'browseable', 'no')
config.append_section(SmbConfigFile::HOMES,
'read only', 'no')
config.append_section(SmbConfigFile::HOMES,
'create mode', '0750')
config.append_section(SmbConfigFile::HOMES,
'guest ok', 'no')
end
if prefs[@admin]
config.append_section(SmbConfigFile::HOMES,
'com.apple: show admin all volumes', prefs[@admin])
end
end
end
class IPAddressOption < NullOption
def initialize(name, text)
@name = name
@text = text
end
def emit(prefs, config)
return unless prefs[@name]
addresses = []
prefs[@name].each { | addr |
ip = (IPAddr.new(addr, Socket::AF_INET) rescue nil)
unless ip
$stderr.print \
"#{$0} (#{@name}): #{addr} is not a valid IPv4 address\n" \
if $VERBOSE
next
end
addresses += [ addr ]
}
config.append(@text, addresses.join(' ')) if addresses.length > 0
end
end
class MasterBrowserOption < NullOption
LocalMaster = 'local'
DomainMaster = 'domain'
def initialize(name, role)
@name = name
@smbrole = role
end
def emit(prefs, config)
val = prefs[@name] ? prefs[@name] : 'none'
case @smbrole.role(prefs)
when ServerRoleOption::PDC
val = DomainMaster
when ServerRoleOption::BDC
val = LocalMaster
end
case
when val.casecmp(DomainMaster) == 0
config.append('domain master', 'yes')
config.append('preferred master', 'yes')
config.append('os level', '65')
config.append_section(SmbConfigFile::STATUS,
'NetBIOS browsing', 'domain master browser')
when val.casecmp(LocalMaster) == 0
config.append('domain master', 'no')
config.append('local master', 'yes')
config.append('preferred master', 'yes')
config.append('os level', '65')
config.append_section(SmbConfigFile::STATUS,
'NetBIOS browsing', 'local master browser')
else
config.append('domain master', 'no')
config.append('local master', 'no')
config.append('preferred master', 'no')
config.append_section(SmbConfigFile::STATUS,
'NetBIOS browsing', 'not a master browser')
return
end
LaunchControl.require(LaunchControl::NMBD)
LaunchControl.require(LaunchControl::SMBD)
end
end
class ServerRoleOption < NullOption
STANDALONE = 'Standalone'
ADS = 'ActiveDirectoryMember'
PDC = 'PrimaryDomainController'
BDC = 'BackupDomainController'
DOMAIN = 'DomainMember'
MACHINE_SCRIPT = '/usr/bin/opendirectorypdbconfig ' +
'-c create_computer_account -r %u -n "/LDAPv3/127.0.0.1"'
USER_SCRIPT = '/usr/bin/opendirectorypdbconfig ' +
'-c create_user_account -r %u -n "/LDAPv3/127.0.0.1"'
def initialize(name, guest)
@name = name
@guestopt = guest
end
def role(prefs)
current_role = prefs[@name]
return STANDALONE unless current_role
case
when current_role.casecmp(DOMAIN) == 0
return DOMAIN
when current_role.casecmp(ADS) == 0
return ADS
when current_role.casecmp(PDC) == 0
return PDC
when current_role.casecmp(BDC) == 0
return BDC
else
return STANDALONE
end
end
def emit(prefs, config)
auth = @guestopt.enabled(prefs) ? "guest odsam" : "odsam";
case self.role(prefs)
when STANDALONE
config.append('security', 'USER')
config.append('use spnego', 'yes')
when ADS
config.append('security', 'ADS')
config.append('use spnego', 'yes')
when DOMAIN
config.append('security', 'DOMAIN')
config.append('domain logons', 'no')
auth = @guestopt.enabled(prefs) ? "guest ntdomain odsam" \
: "ntdomain odsam"
when PDC
config.append('security', 'USER')
config.append('add machine script', MACHINE_SCRIPT)
config.append('add user script', USER_SCRIPT)
add_domain_logon(config)
when BDC
config.append('security', 'USER')
add_domain_logon(config)
end
config.append('auth methods', auth)
config.append_section(SmbConfigFile::STATUS,
'Server role', self.role(prefs))
end
def add_domain_logon(config)
config.append('domain logons', 'yes')
config.append_section(SmbConfigFile::HOMES,
'root preexec', '/usr/sbin/inituser %U')
config.append('logon drive', 'H:')
config.append('logon path', '\\%N\profiles%u')
config.append_section(SmbConfigFile::NETLOGON, 'path', '/etc/netlogon')
config.append_section(SmbConfigFile::NETLOGON, 'browseable', 'no')
config.append_section(SmbConfigFile::NETLOGON, 'write list', '@admin')
config.append_section(SmbConfigFile::NETLOGON, 'oplocks', 'yes')
config.append_section(SmbConfigFile::NETLOGON, 'strict locking', 'no')
config.append_section(SmbConfigFile::PROFILES,
'path', '/Users/Profiles')
config.append_section(SmbConfigFile::NETLOGON, 'browseable', 'no')
config.append_section(SmbConfigFile::NETLOGON, 'read only', 'no')
config.append_section(SmbConfigFile::NETLOGON, 'oplocks', 'yes')
config.append_section(SmbConfigFile::NETLOGON, 'strict locking', 'no')
end
end
class PrefsChangedOption < NullOption
@@force_change = false
def PrefsChangedOption.force_change
@@force_change = true
end
def initialize(name, path)
@name = name
@path = path
@prev_sig, @prev_gen = get_config_signatures(@path)
end
def emit(prefs, config)
LaunchControl.no_config_change unless self.changed(prefs)
if prefs[@name]
config.append_section(SmbConfigFile::STATUS,
'Preferences signature', prefs[@name])
end
config.append_section(SmbConfigFile::STATUS,
'Preferences generator', $ID)
end
def changed(prefs)
curr_sig = prefs[@name]
if @@force_change
return true
end
if curr_sig == nil || curr_sig != @prev_sig
return true
end
if @prev_gen == nil || @prev_gen != $ID
return true
end
return false
end
private
def get_config_signatures(path)
signature = nil
generator = nil
begin
File.open(path, 'r') { | fd |
fd.each_line { | line |
if line.match(/Preferences signature: ([abcdef0-9]+)/)
signature = $1
end
if line.match(/Preferences generator: (\$Id*\$)/)
generator = $1
end
if (signature && generator)
return [signature, generator]
end
}
}
return [signature, generator]
rescue Exception => err
if $VERBOSE
$stderr.print "#{$0}: unable to read signatures from #{path}\n"
$stderr.print "#{$0}: #{err}\n"
end
end
return [nil, nil]
end
end
class SmbConfigFile
SmbConfigPath = '/etc/smb.conf'
SmbRunConfigPath = '/var/run/smb.conf'
GLOBAL = 'global'
PRINTERS = 'printers'
HOMES = 'homes'
NETLOGON = 'netlogon'
PROFILES = 'profiles'
STATUS = '_internal_only_'
def initialize()
@config = {
GLOBAL => {},
HOMES => {},
NETLOGON => {},
PROFILES => {},
PRINTERS => {},
STATUS => {}
}
end
def sections
return @config.keys
end
def lines(section)
unless @config.has_key? section
raise ArgumentError, "invalid config section #{section}", caller
end
@config[section].keys.sort.each { | key |
yield key, @config[section][key]
}
end
def has_section?(section)
unless @config.has_key? section
raise ArgumentError, "invalid config section #{section}", caller
end
return @config[section].length > 0 ? true : false
end
def append(key, value)
append_section(GLOBAL, key, value)
end
def append_section(section, key, value)
unless @config.has_key? section
raise ArgumentError, "invalid config section #{section}", caller
end
@config[section][key.downcase] = value
end
end
class SmbPreferences
SERVER = 1
DESKTOP = 2
AppID = 'com.apple.smb.server'
DefaultPrefsPath = \
'/System/Library/CoreServices/SmbFileServer.bundle/Resources'
ServerDefaultPrefs = "#{DefaultPrefsPath}/ServerDefaults.plist"
DesktopDefaultPrefs = "#{DefaultPrefsPath}/DesktopDefaults.plist"
BUILTIN =
{
'NetBIOSName' => nil,
'NetBIOSNodeType' => nil,
'NetBIOSScope' => nil,
'WINSServerAddressList' => [],
'Workgroup' => 'WORKGROUP',
'KerberosRealm' => nil,
'LocalKerberosRealm' => nil,
'SuspendServices' => false,
'EnabledServices' => [],
'ServerRole' => ServerRoleOption::STANDALONE,
'ServerDescription' => 'Mac OS X',
'AllowGuestAccess' => false,
'MaxClients' => 10 ,
'AllowKerberosAuth' => true,
'AllowNTLMAuth' => true,
'AllowLanManAuth' => false,
'LoggingLevel' => 1,
'DOSCodePage' => '437',
'MasterBrowser' => false,
'RegisterWINSName' => false,
'VirtualHomeShares' => true,
'VirtualAdminShares' => true,
'PasswordServer' => nil,
'PreferencesSignature' => nil,
}
def SmbPreferences.system_type
is_server = false
ShellCommand.run(ShellCommand::SW_VERS, '-productName') { |line|
is_server = true if line =~ /Server/
}
return (is_server ? SERVER : DESKTOP)
end
def SmbPreferences.defaults
prefs = {}
case system_type()
when SERVER
prefs = Preferences.load_plist(ServerDefaultPrefs)
when DESKTOP
prefs = Preferences.load_plist(DesktopDefaultPrefs)
end
unless prefs
$stderr.print \
"#{$0}: failed to load default preferences\n"
return {}
end
return prefs
end
def initialize
@current = BUILTIN
self.merge_prefs(SmbPreferences.defaults)
end
def merge_prefs(prefs)
keys = @current.keys
keys.each { | key |
next unless prefs.has_key?(key)
val = prefs[key]
$stderr.print \
"updating #{key} from '#{@current[key]}' to '#{val}'\n" \
if $DEBUG
@current[key] = val
}
end
def each
@current.each do |key, value|
yield key, value
end
end
def [](key)
return @current[key]
end
def sync_config(optionlist)
File.open(SmbConfigFile::SmbRunConfigPath, "w") { | fd |
config_lines(optionlist) { | line |
fd.write(line)
}
fd.fsync
}
end
def print_config(optionlist)
config_lines(optionlist) { | line | $stdout.print line }
end
private
def config_lines(optionlist)
config = SmbConfigFile.new()
optionlist.each do |option|
begin
option.emit(@current, config)
rescue Exception => err
if $VERBOSE
$stderr.print "#{$0}: failed to handle option #{option}\n"
$stderr.print "#{$0}: #{err}\n"
end
end
end
yield <<EOF
EOF
config.lines(SmbConfigFile::STATUS) { | key, value |
yield "# #{key.capitalize}: #{value}\n"
}
yield "#\n\n"
config.sections.each { |section|
next if section == SmbConfigFile::STATUS
if config.has_section? section
yield "\n[#{section}]\n"
config.lines(section) { | key, value |
yield " #{key} = #{value}\n"
}
end
}
end
end
class Command
OPTIONS = [
SimpleOption.new('NetBIOSName', 'netbios name'),
NullOption.new('NetBIOSNodeType'),
SimpleOption.new('NetBIOSScope', 'netbios scope'),
SimpleOption.new('Workgroup', 'workgroup'),
NullOption.new('AllowKerberosAuth'),
NullOption.new('AllowNTLM2Auth'),
SimpleOption.new('AllowNTLMAuth', 'ntlm auth'),
SimpleOption.new('AllowLanManAuth', 'lanman auth'),
SimpleOption.new('ServerDescription', 'server string'),
SimpleOption.new('MaxClients', 'max smbd processes'),
SimpleOption.new('LoggingLevel', 'log level'),
SimpleOption.new('DOSCodePage', 'dos charset'),
KerberosRealmOption.new('KerberosRealm', 'LocalKerberosRealm'),
GuestAccessOption.new('AllowGuestAccess'),
MasterBrowserOption.new('MasterBrowser',
ServerRoleOption.new('ServerRole', nil)),
PrefsChangedOption.new('PreferencesSignature',
SmbConfigFile::SmbRunConfigPath),
WinsRegisterOption.new('RegisterWINSName', 'WINSServerAddressList'),
SimpleOption.new('PasswordServer', 'password server'),
ServicesOption.new('EnabledServices'),
SuspendOption.new('SuspendServices'),
AutoSharesOption.new('VirtualHomeShares', 'VirtualAdminShares'),
ServerRoleOption.new('ServerRole',
GuestAccessOption.new('AllowGuestAccess'))
]
def Command.SyncPrefs
begin
smbopts = SmbPreferences.new()
smbopts.merge_prefs(Preferences.new(SmbPreferences::AppID))
smbopts.sync_config(OPTIONS)
rescue StandardError => err
$stderr.print "#{$0}: #{err}\n" if $VERBOSE
return 1
end
LaunchControl.init
LaunchControl.sync
return 0
end
def Command.ListPending
smbopts = SmbPreferences.new()
smbopts.merge_prefs(Preferences.new(SmbPreferences::AppID))
smbopts.print_config(OPTIONS)
return 0
end
def Command.ListDefaults
smbopts = SmbPreferences.new()
smbopts.print_config(OPTIONS)
return 0
end
def Command.ChangesPending
if Command.need_pref_sync
print "#{$0}: configuration is out of date\n" if $VERBOSE
return 0
else
print "#{$0}: configuration is current\n" if $VERBOSE
return 2
end
end
private
def Command.need_pref_sync
prefs = Preferences.new(SmbPreferences::AppID)
check = PrefsChangedOption.new('PreferencesSignature',
SmbConfigFile::SmbRunConfigPath)
return check.changed(prefs)
end
end
opts = OptionParser.new
opts.on('--verbose',
'print extra debugging messages') {
$VERBOSE = true
}
opts.on('--linger=ARG', Integer,
'stick around and sync updates until ARG', 'seconds of inactivity') { |val|
$LINGER = val.to_i
}
opts.on('--force-sync',
'force synchronization even if it is', 'unnecessary') {
PrefsChangedOption.force_change
}
opts.on('--restart-services',
'restart any services that are', 'already running') {
LaunchControl.force_restart
}
opts.on('--suspend-services',
"leave all services disabled") {
LaunchControl.force_disable
}
opts.on('--changes-pending',
'exit with 0 status if there are', 'unsynchronized changes') {
exit Command.ChangesPending
}
opts.on('--list-pending',
'print the pending configuration, but do', 'not synchronize') {
exit Command.ListPending
}
opts.on('--list-defaults',
"print the default configuration and exit") {
exit Command.ListDefaults
}
begin
opts.parse!(ARGV) if (ARGV.length != 0)
raise OptionParser::InvalidOption, ARGV[0], caller
end
rescue OptionParser::ParseError => err
$stderr.print "#{$0}: #{err}\n"
$stderr.print opts.help()
exit 1
end
if $LINGER <= 0
exit Command.SyncPrefs
else
Command.SyncPrefs
print "#{$0}: lingering for #{$LINGER}s\n" if $VERBOSE
stop = Time.now() + $LINGER
path = \
"/Library/Preferences/SystemConfiguration/#{SmbPreferences::AppID}.plist"
last = File.mtime(path)
while Time.now() < stop
sleep 0.2
current = File.mtime(path)
if last != current
last = current
Command.SyncPrefs
stop = Time.now() + $LINGER
end
end
exit 0
end