1.0b4
Mac OS X 10.1 introduced a new API, the System Configuration framework (SCF), that allows you to programmatically configure network settings. SCF provides a nice, abstract, extensible API for manipulating persistent preferences, however it is not a high-level API. Doing common operations, like creating a new set (known as a "location" in the user interface), requires you to combine a number of diverse APIs from I/O Kit, System Configuration, and Core Foundation.
MoreSCF is an attempt to make this easier. It is modeled after the MoreNetworkSetup sample code for traditional Mac OS. It provides a library of code to do all of the common things that the user might do in the Network preferences panel. For example, you can create a new location, duplicate a location, active and inactivate services (known as "ports" in the user interface), and so on. MoreSCF also provides high-level APIs to do common operations, such as creating a new set that contains a single PPP dialup service.
System Configuration framework was introduced in Mac OS X 10.1. MoreSCF requires that system or later.
The sample contains the following items:
The sample includes a compiled version of the
MoreSCFTest-Debug command line tool. This tool modifies
the SCF preferences, which means you must run it as root. To do this,
launch Terminal, change to the MoreSCFTest directory, and run
MoreSCFTest-Debug using the sudo command.
The following is an example of doing this.
[localhost:~] quinn% cd /Volumes/Tweedlebug/DTS\ \ Work/MIBWork/MoreIsBetter/MIB-Libraries/MoreSCF/MoreSCFTest/ [localhost:MIB-Libraries/MoreSCF/MoreSCFTest] quinn% sudo MoreSCFTest-Debug -lq Password: enter your password here MoreSCFTest-Debug Running all tests LeakTest(TestPortScanner) MoreSCFPortScanner.c: You should install a port name callback. LeakTest(TestCopySetsDict) LeakTest(TestSetEnumerationAndSwitch) LeakTest(TestDuplicateAndDeleteSet) LeakTest(TestRenameSet) LeakTest(TestServiceEnumerate) LeakTest(TestDuplicateAndDeleteService) LeakTest(TestRenameService) LeakTest(TestEnumerateEntities) LeakTest(TestCreateSet) LeakTest(TestISP) LeakTest(TestAppleTalk) [localhost:MIB-Libraries/MoreSCF/MoreSCFTest] quinn%
The tool takes quite some time to run (up to a few minutes on my ancient test machine). As long as it runs to completion it should leave your network preferences how it found them. However, if you stop it halfway through you may need to manually restore your preferences.
Two of the tests requires that you have a location called "DefaultLocationForMoreSCFTest". You should create this location using the Network panel of System Preferences. Just create the location and leave its settings unmodified. The test uses this location to verify that a new set created by MoreSCF exactly matches a default set created by the control panel.
|
Note:
|
The sample was built using CodeWarrior Pro 8.3 on Mac OS X 10.2.x. It also builds with Project Builder 2.1 (from the Mac OS X December 2002 developer tools). Your mileage may vary with other environments.
The project is built as a Mach-O binary. It is not possible to call SCF directly from a CFM binary. If you need to do this you should either factor your SCF code into a separate Mach-O bundle or use one of the techniques illustrated in the CallMachOFramework sample code.
The CodeWarrior project contains two targets. You should build the "Mach-O" target. I use the "Mach-O, C++" to test C++ compatibility. You typically wouldn't use this target.
This section describes a number of things to keep in mind while working with SCF, or indeed MoreSCF.
[localhost:~] quinn% cp /var/db/SystemConfiguration/preferences.xml \ /var/db/SystemConfiguration/preferencesBU.xml [localhost:~] quinn% cp /var/db/SystemConfiguration/preferencesBU.xml \ /var/db/SystemConfiguration/preferences.xml
scutil, that lets you look at the current state of
the SCF dynamic store. You can use this utility to see how your
SCF code affects the actual network configuration. The following
is a simple example of how to use scutil.[localhost:~] quinn% scutil
> open
> list
subKey [0] = File:/var/run/lookupd.pid
subKey [1] = File:/var/run/nibindd.pid
subKey [2] = Plugin:IPConfiguration
subKey [3] = Setup:
subKey [4] = Setup:/
subKey [5] = Setup:/Network/Global/IPv4
subKey [6] = Setup:/Network/Service/26
subKey [7] = Setup:/Network/Service/26/AirPort
subKey [8] = Setup:/Network/Service/26/DNS
subKey [9] = Setup:/Network/Service/26/IPv4
subKey [10] = Setup:/Network/Service/26/Interface
subKey [11] = Setup:/Network/Service/26/Proxies
subKey [12] = Setup:/System
subKey [13] = State:/Network/Global/DNS
subKey [14] = State:/Network/Global/IPv4
subKey [15] = State:/Network/Global/Proxies
subKey [16] = State:/Network/Interface
subKey [17] = State:/Network/Interface/en0/Link
subKey [18] = State:/Network/Interface/en1/IPv4
subKey [19] = State:/Network/Interface/en1/Link
subKey [20] = State:/Network/Interface/lo0/IPv4
subKey [21] = State:/Network/Service/26/IPv4
subKey [22] = State:/Users/ConsoleUser
subKey [23] = daemon:AppleFileServer
> get Setup:/Network/Service/26/AirPort
> d.show
<dictionary> {
MACAddress : 00:30:65:14:1e:1e
PreferredNetwork : QuinnNet
}
> help
Available commands:
help : list available commands
f.read file : process commands from file
d.init : initialize (empty) dictionary
d.show : show dictionary contents
d.add key [*#?] val [v2 ...] : add information to dictionary
(*=array, #=number, ?=boolean)
d.remove key : remove key from dictionary
open : open a session with "configd"
close : close current "configd" session
list [pattern] : list keys in data store
add key ["temporary"] : add key in data store w/current dict
get key : get dict from data store w/key
set key : set key in data store w/current dict
remove key : remove key from data store
notify key : notify key in data store
n.list ["pattern"] : list notification keys
n.add key ["pattern"] : add notification key
n.remove key ["pattern"] : remove notification key
n.changes : list changed keys
n.watch [verbose] : watch for changes
n.cancel : cancel notification requests
> quit
[localhost:~] quinn%
If you are using SCF to modify system preferences, you'll quickly
discover that your program needs to run as root (with an effective
user ID of 0) to avoid the dreaded error 1003
(kSCStatusAccessError). There are two simple ways to get
around this during debugging.
sudo -s, turns your normal Terminal window
into a root window. The second command launches CodeWarrior as
root. Note that the command you actually execute is
LaunchCFMApp, passing it as a parameter the full path
to your CodeWarrior executable; you'll have to customize this path
depending on where you installed CodeWarrior. Also note the
'&' at the end of the command line, which requests the shell
not to wait for CodeWarrior to quit before accepting a new
command. The third command launches Project Builder as root.[localhost:~] quinn% sudo -s Password:******** [localhost:~] root# /System/Library/Frameworks/Carbon.framework/\ Versions/A/Support/LaunchCFMApp /Volumes/Tweedlebug/Languages/CWPro7MIB/\ CodeWarrior\ IDE\ 4.2.5\ \* & [1] 2030 [localhost:~] root# /Developer/Applications/Project\ Builder.app/\ Contents/MacOS/Project\ Builder & [2] 2031 [localhost:~] root#
|
IMPORTANT: |
The correct way to use MoreSCF depends on the exact needs of your program. The following sections explore this issue in detail.
MoreSCF is built as part of the DTS sample code library MoreIsBetter. It only uses the CoreServices framework, which means you should be able to use it in non-Carbon programs. For example, Cocoa application developers can use MoreSCF very easily. Making MoreSCF independent of CoreServices would be tricky because MoreSCF needs to be able to locate the Modem Scripts folder which can only be done with CoreServices APIs.
MoreSCF makes extensive use of asserts. When you integrate MoreSCF
into your build system you should ensure that
assert maps to whatever assert mechanism you are
using in your project.
MoreSCF's asserts act like comments for the exact function semantics. Specifically, if you want to know whether NULL is legal for a particular parameter, look at the asserts at the top of the routine.
MoreSCF follows the Core Foundation (CF) coding convention in that
routines with "Create" or "Copy" in the name return a reference which
you must release, whereas other routines (most commonly with "Get" in
the name) return a reference that you don't need to release. However,
unlike CF, MoreSCF returns OSStatus error codes from
virtually all functions. When you call a MoreSCF routine that
allocates an object, you get the result by passing the address of a
variable as a parameter to the routine. You must initialize this
variable to NULL before calling the routine. The value of the
variable will be NULL if the routine returns an error.
MoreSCF consists of a number of related modules. As a rule I recommend that you use the highest level routines that will get the job done. This section describes each of the MoreSCF modules, how they relate, and what you can do with them.
This module contains a routine that a creates a list of CCLs (modem scripts) installed in the system and returns that list along with an indication of which CCL should be the default. An Internet setup assistant would use this routine to present the user with a list of CCLs to choose from if they are connecting via a modem.
It is unlikely that you would need to customize the routines in this module.
This module contains code that searches the I/O Registry for all possible network ports. For each port it returns the information necessary to create an SCF service that uses the port. This facility is crucial when you create a new set using SCF.
This is not as easy as it sounds. The code is derived from code originally written by the engineer who wrote the Network preferences panel. It contains many interesting heuristics to decide whether a port is suitable for networking and to determine its user visible name.
If you link this module into a GUI-based application you should
install a port name callback (using
MoreSCFSetPortNameCallback) so that you can localize the
user visible names of the ports.
You should probably use the routines in this module unmodified unless you experience compatibility problems.
This module contains code that allows you to easily create a SCF entity from a C structure. It takes care of many of the fiddly details of this process, such as converting IP addresses to strings, encoding PPP passwords, and so on.
In most cases you would use the routines from this module unmodified, although sometimes the routines provide a simplified view of certain preferences and you might want to override some of their default decisions.
This module is layered on top of the previous two modules. It provides a mid-level API for manipulating SCF preferences. The module exports four categories of routines:
I expect that you would use the routines from this module unmodified. If you need to do complex SCF operations you'll probably find that either a) an appropriate routine is already provided in MoreSCFHelpers, or b) you can achieve the results you want by calling various routines exported by this module.
This module is layered on top of MoreSCF and exports routines for doing very high-level operations using SCF, such as
MoreSCSetAppleTalkActive),MoreSCDHCPRelease),
andMoreSCMakeNewDialupSet and MoreSCMakeNewPPPoESet).You should treat these routines as sample code; if they don't meet your exact needs you should modify them appropriately. For example, MoreSCMakeNewPPPoESet provides no way to create a PPPoE location for an AirPort interface, but it is simple to modify it to do so.
This section describes the MoreSCF routines needed to do the most common high-level operations.
MoreSCF uses a single connection to SCF preferences for all of its
work. To support a flexible API MoreSCF references counts that
reference. The first time you call MoreSCOpen, MoreSCF
creates a SCPreferencesRef and stores it in a global
variable. Every subsequent time you call the MoreSCOpen,
it simply increments the reference count. Every time you call
MoreSCClose, it decrements the reference count. When the
reference count drops to zero, MoreSCF commits any changes you might
have made, unlocks the preferences if you have locked them, and then
closes the SCPreferencesRef.
The primary rationale for this approach is that I did not want clients of MoreSCF to be forced to call MoreSCOpen. Thus, each
exported MoreSCF routine calls MoreSCOpen when it starts
and MoreSCClose when it finishes. This presented an
interesting problem when MoreSCF routines called other MoreSCF
routines. I wanted all of the MoreSCF routines to share the same
SCPreferencesRef (otherwise they wouldn't see each
others changes until they committed them), however I didn't want to
cache the SCPreferencesRef forever because that caused
other problems with preference coherency. My ultimate solution was to
simply reference count the SCPreferencesRef.
Given the above there are some cases when you don't need to call
MoreSCOpen at all. Each MoreSCF API routine will call
MoreSCOpen for you. However, in most circumstances you
should call MoreSCOpen and MoreSCClose.
This has a number of advantages.
MoreSCOpen, MoreSCF creates an
SCPreferencesRef. This in turn holds a snapshot of
the current preferences. You can then make subsequent MoreSCF
calls and be assured that you're seeing a coherent copy of the
preferences.MoreSCOpen/MoreSCClose pair will be
faster because they won't be continually creating and disposing of
their connection to SCF preferences.MoreSCOpen so that you can lock the
preferences and ensure that your changes are coherent.The standard calling sequence, used for the first two cases above, is shown below.
(void) MoreSCSetClient(CFSTR("MyClientName")); // optional
err = MoreSCOpen(false, false); // no locking
if (err == noErr) {
// use various MoreSCF routines in here
}
MoreSCClose(&err, false); // no changes
If you are making changes you would probably use the next sequence.
err = MoreSCOpen(true, true); // lock prefs
if (err == noErr) {
// read preferences using MoreSCF routines
// make decisions based on what you read
// write preferences using MoreSCF routines
}
MoreSCClose(&err, true); // force commit
MoreSCF also provides a routine
(MoreSCGetSCPreferencesRef) that allows you to access
the underlying SCPreferencesRef. This is useful in cases
where you need to call SCF preferences routines that do things
outside of the realm of MoreSCF (for example, calling
SCPreferencesGetSignature).
Most people look at MoreSCF because they're writing an Internet setup assistant (ISA). MoreSCF contains routines that specifically help you to do this. However, before I discuss these routines I should stress one key point.
Apple strongly recommends that ISA developers create a new set (known as a "location" in the user interface) for their specific service. This prevents your settings from overwriting the user's prized network setup and allows the user a quick escape (switching back to their previous location) if your setup assistant doesn't work properly.
If you're writing an ISA, the three keys routines are MoreSCFindSetByUserVisibleNameAndCopyID, MoreSCMakeNewDialupSet and MoreSCMakeNewPPPoESet. The first routine lets you determine whether your ISA has already been run and created your service's location. The other two let you create a new location with one active dialup or PPPoE service. All of these routines are described in comments in "MoreSCFHelpers.h". You can also look at the TestISP routine in "MoreSCFTest.c" for a very simple example of their use.
The above routines are very easy to use if you want to use the default modem (or Ethernet) port. If you want to provide the user a list of modem ports to choose from, you should call the MoreSCCreatePortArray routine (declared in
"MoreSCFPortScanner.h") to get the list of network ports and their
associated user-visible names and then supply the BSD name of the
port to MoreSCMakeNewDialupSet or MoreSCMakeNewPPPoESet. For dialup you should also use MoreSCCreateCCLArray (from "MoreSCFCCLScanner.h") to
build a list of CCLs (modem scripts) that the user can use for that
modem.
The routines to enumerate and switch sets are all declared in
"MoreSCF.h". You can use MoreSCCopySetIDs to get an
array of set IDs, MoreSCCopyUserVisibleNameOfSet to get
the user-visible name of a set given its set ID, and
MoreSCSetCurrentSet to make a particular set the active
set.
MoreSCF exports routines to
These routines are fairly well documented by the comments in the "MoreSCF.h" header file. Most of the routines are used somewhere within MoreSCF, which you can look at for an example of their use.
The following is a list of odd things you might encounter while working with MoreSCF and things I would improve within MoreSCF if I had the time.
MoreSCDeleteService allows you can delete the
last service for a hardware port within a set. This is bad because
the Network preferences panel doesn't let you do this (in the
Network panel you would inactivate rather than delete that port).
MoreSCDeleteService should enforce this restriction.
I know how to code this but it wasn't of sufficiently high
priority to make this release.
MoreSCCreateAirPortEntity isn't flexible enough
to create all types of AirPort entities. There are a number of
keys that are described in "SCSchemaDefinitions.h" that can't be
set by calling MoreSCCreateAirPortEntity. I need to
research what these keys do and how best to reflect them in
MoreSCAirPortDigest [3138423]. See the comment in MoreSCCreateAirPortEntity for a list of the keys in
question.MoreSCCopyEntities
(MoreSCCopyEntitiesMutable).MoreSCCreateCCLArray (in "MoreSCFCCLScanner.c") contains a hardwired name for the "Apple Internal 56K Modem (v.90)" modem script. If this script isn't present it chooses the first script in the first Modem Scripts folder. This is the same behavior as the Network panel of System Preferences. You can (and generally should) override this default when you create a new modem service. You can do this using MoreSCSetDefaultCCL. Ideally Apple should provide an intelligent modem detection library that can query the modem and determine the correct script. As this currently isn't available, MoreSCF provides a routine to return the information necessary to build a modem scripts popup menu so that the user can choose the correct script by hand.SCPreferencesRef, SCF creates a
coherent in-memory copy of the preferences database. When you make
changes to the SCPreferencesRef, you're actually
making changes to the memory copy. When you commit those changes,
SCF writes the database to disk and notifies other users that the
database has changed. This sequence has important consequences for
the design of MoreSCF. I used to open one
SCPreferencesRef and keep it open for the lifetime of
the library in memory. However, this architecture meant that, if
someone else changed the database, clients of MoreSCF would never
see that change. My new design involves reference counting. When
the reference count drops to zero, MoreSCF closes its
SCPreferencesRef. The next time a client calls
MoreSCOpen MoreSCF creates a new copy of the
SCPreferencesRef and picks up any changes made by any
other SCF clients.MoreSCClose will return an error. Because we
only commit when the reference count drops to zero, an error from
MoreSCClose indicates that we attempted to commit and
failed (hence the changes aren't on disk) and we also closed the
SCPreferencesRef (hence the changes are no longer in
memory). When you next call MoreSCOpen you will get a
fresh, coherent copy of the preferences from disk, including any
changes made by other SCF clients. In summary, the outermost
MoreSCClose is a commit or rollback operation; if it
returns noErr, the commit was successful; if it
returns an error, the commit failed and we roll you back to the
last coherent database state (which may not be the state that you
last saw).If you find any problems with this sample, mail DTS and we will try to fix them up.
MakeNewSeAndCopyIDAndPath in "MoreSCF.c". While I had
the code open I also fixed some minor stuff, like changing the
default PPP option values.MoreSCSetDefaultCCL.
MoreSCMakeNewDialupSet to support new features (MoreSCMakeNewPPPoESet) and the newly exported routine (MoreSCMakeNewSingleServiceSet).
Share and Enjoy.
Worldwide Developer Technical Support
26 Feb 2003