Read Me About MoreSCF

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.

Packing List

The sample contains the following items:

Using the Sample

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:
Under some circumstances the TestCreateSet test will fail because the newly created set doesn't exactly match the contents of "DefaultLocationForMoreSCFTest". There are a few common reasons for this.

  • Your "DefaultLocationForMoreSCFTest" set might be is out of date; to rectify this simply open the control panel, deleting the existing "DefaultLocationForMoreSCFTest" location and create a new one.
  • You might have NetInfo enabled; to rectify this, turn off NetInfo using the "Directory Setup" application, then update your "DefaultLocationForMoreSCFTest" set as described previously.
  • The "DefaultLocationForMoreSCFTest" might be set to use non-DHCP address acquisition (in the "Configure" popup menu). This happens if you create the new set when your current set is not using DHCP. In that case the Network preferences panel creates a set with the same address acquisition mode as your current set. This anomalous behavior is not replicated by MoreSCF. To make TestCreateSet run without error, open the Network preferences panel and modify the services within the "DefaultLocationForMoreSCFTest" set to acquire address via PPP.

Building the Sample

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.

SCF Hints and Tips

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
[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% 

Debugging Root Binaries

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.

[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:
This approach, while fine for debugging, is not appropriate for your final product. Ultimately you will need a better solution, possibly including a "set user ID" binary that does your actual SCF work. The DTS sample code MoreAuthSample shows the recommended technique for doing this.

Using MoreSCF In Your Code

The correct way to use MoreSCF depends on the exact needs of your program. The following sections explore this issue in detail.

Coding Conventions

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.

Big Picture

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.

MoreSCFCCLScanner

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.

MoreSCFPortScanner

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.

MoreSCFDigest

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.

MoreSCF

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.

MoreSCFHelpers

This module is layered on top of MoreSCF and exports routines for doing very high-level operations using SCF, such as

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.

Getting Started

This section describes the MoreSCF routines needed to do the most common high-level operations.

Opening, Closing, Locking

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.

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).

Creating a New Dialup Set

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.

Enumerating and Switching Sets

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.

Manipulating Sets, Services, and Entities

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.

Caveats

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.

Credits and Version History

If you find any problems with this sample, mail DTS and we will try to fix them up.

Share and Enjoy.

Worldwide Developer Technical Support
26 Feb 2003