Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1687,10 +1687,15 @@ AC_DEFINE(OF_HAVE_APPLETALK, 1, [Whether we have AppleTalk]) AC_SUBST(USE_SRCS_APPLETALK, '${SRCS_APPLETALK}') AC_CHECK_MEMBERS([struct ifreq.ifr_name], [ + AC_DEFINE(OF_HAVE_APPLETALK_IFCONFIG, 1, + m4_normalize([ + Whether AppleTalk interfaces + can be configured + ])) AC_SUBST(OFATALKCFG, ofatalkcfg) ], [], [ #ifdef OF_HAVE_SYS_SOCKET_H # include #endif Index: src/OFDDPSocket.h ================================================================== --- src/OFDDPSocket.h +++ src/OFDDPSocket.h @@ -16,10 +16,64 @@ #import "OFDatagramSocket.h" OF_ASSUME_NONNULL_BEGIN @class OFString; +@class OFDictionary OF_GENERIC(KeyType, ObjectType); + +#ifdef OF_HAVE_APPLETALK_IFCONFIG +/** + * @brief A dictionary mapping keys of type @ref + * OFAppleTalkInterfaceConfigurationKey. + */ +typedef OFConstantString *OFAppleTalkInterfaceConfigurationKey; + +/** + * @brief A key for OFAppleTalkInterfaceConfiguration. + * + * Possible keys are: + * * @ref OFAppleTalkInterfaceConfigurationNetwork + * * @ref OFAppleTalkInterfaceConfigurationNode + * * @ref OFAppleTalkInterfaceConfigurationPhase + * * @ref OFAppleTalkInterfaceConfigurationNetworkRange + */ +typedef OFDictionary OF_GENERIC(OFAppleTalkInterfaceConfigurationKey, id) + *OFAppleTalkInterfaceConfiguration; + +/** + * @brief The AppleTalk network of an interface. + * + * The corresponding value is of type @ref OFNumber in the range 0 to 65535. + */ +extern const OFAppleTalkInterfaceConfigurationKey + OFAppleTalkInterfaceConfigurationNetwork; + +/** + * @brief The AppleTalk node of an interface. + * + * The corresponding value is of type @ref OFNumber in the range 0 to 255. + */ +extern const OFAppleTalkInterfaceConfigurationKey + OFAppleTalkInterfaceConfigurationNode; + +/** + * @brief The AppleTalk phase of an interface. + * + * The corresponding value is of type @ref OFNumber in the range 1 to 2. + */ +extern const OFAppleTalkInterfaceConfigurationKey + OFAppleTalkInterfaceConfigurationPhase; + +/** + * @brief The AppleTalk network range of an interface. + * + * The corresponding value is of type @ref OFPair with two @ref OFNumber, both + * in the range 0 to 65535. + */ +extern const OFAppleTalkInterfaceConfigurationKey + OFAppleTalkInterfaceConfigurationNetworkRange; +#endif /** * @protocol OFDDPSocketDelegate OFDDPSocket.h ObjFW/OFDDPSocket.h * * @brief A delegate for OFDDPSocket. @@ -66,10 +120,50 @@ * @note The delegate is retained for as long as asynchronous operations are * still ongoing. */ @property OF_NULLABLE_PROPERTY (assign, nonatomic) id delegate; + +#ifdef OF_HAVE_APPLETALK_IFCONFIG +/** + * @brief Configures the specified interface with the specified AppleTalk + * configuration. + * + * @param configuration The AppleTalk configuration for the interface. See + * @ref OFAppleTalkInterfaceConfiguration for more + * details. + * @param interfaceName The name of the interface to configure for AppleTalk + * @throw OFSetOptionFailedException Setting the configuration failed. Consult + * errNo for details. + */ ++ (void)setConfiguration: (OFAppleTalkInterfaceConfiguration)configuration + forInterface: (OFString *)interfaceName; + +/** + * @brief Returns the AppleTalk configuration for the specified interface. + * + * @param interfaceName The name of the interface for which to return the + * AppleTalk configuration + * @return The AppleTalk configuration for the specified interface, or `nil` if + * the specified interface is not configured for AppleTalk. See + * @ref OFAppleTalkInterfaceConfiguration for more details. + * @throw OFGetOptionFailedException Getting the configuration failed. Consult + * errNo for details. + */ ++ (nullable OFAppleTalkInterfaceConfiguration) + configurationForInterface: (OFString *)interfaceName; + +/** + * @brief Removes the AppleTalk configuration for the specified interface. + * + * @param interfaceName The name of the interface for which to remove the + * AppleTalk configuration + * @throw OFSetOptionFailedException Removing the configuration failed. Consult + * errNo for details. + */ ++ (void)removeConfigurationForInterface: (OFString *)interfaceName; +#endif /** * @brief Bind the socket to the specified network, node and port. * * @param network The network to bind to. 0 means any. Index: src/OFDDPSocket.m ================================================================== --- src/OFDDPSocket.m +++ src/OFDDPSocket.m @@ -20,19 +20,32 @@ #ifdef HAVE_FCNTL_H # include #endif #import "OFDDPSocket.h" +#import "OFDictionary.h" +#import "OFNumber.h" +#import "OFPair.h" #import "OFSocket.h" #import "OFSocket+Private.h" #import "OFAlreadyOpenException.h" #import "OFBindDDPSocketFailedException.h" +#import "OFGetOptionFailedException.h" #import "OFInvalidArgumentException.h" #import "OFNotOpenException.h" +#import "OFOutOfRangeException.h" #import "OFReadFailedException.h" +#import "OFSetOptionFailedException.h" #import "OFWriteFailedException.h" + +#ifdef HAVE_NET_IF_H +# include +#endif +#ifdef HAVE_SYS_IOCTL_H +# include +#endif #ifdef OF_HAVE_NETAT_APPLETALK_H # include # include @@ -43,13 +56,195 @@ struct at_addr address, router; unsigned short netStart, netEnd; at_nvestr_t zoneName; }; #endif + +#ifdef OF_HAVE_APPLETALK_IFCONFIG +const OFAppleTalkInterfaceConfigurationKey + OFAppleTalkInterfaceConfigurationNode = + @"OFAppleTalkInterfaceConfigurationNode"; +const OFAppleTalkInterfaceConfigurationKey + OFAppleTalkInterfaceConfigurationNetwork = + @"OFAppleTalkInterfaceConfigurationNetwork"; +const OFAppleTalkInterfaceConfigurationKey + OFAppleTalkInterfaceConfigurationPhase = + @"OFAppleTalkInterfaceConfigurationPhase"; +const OFAppleTalkInterfaceConfigurationKey + OFAppleTalkInterfaceConfigurationNetworkRange = + @"OFAppleTalkInterfaceConfigurationNetworkRange"; +#endif @implementation OFDDPSocket @dynamic delegate; + +#ifdef OF_HAVE_APPLETALK_IFCONFIG ++ (void)setConfiguration: (OFAppleTalkInterfaceConfiguration)config + forInterface: (OFString *)interfaceName +{ + OFNumber *network, *node, *phase; + OFPair OF_GENERIC(OFNumber *, OFNumber *) *range; + int sock; + struct ifreq request; + struct sockaddr_at *sat; + uint16_t rangeStart, rangeEnd; + + if (interfaceName.UTF8StringLength > IFNAMSIZ - 1) + @throw [OFOutOfRangeException exception]; + + network = [config + objectForKey: OFAppleTalkInterfaceConfigurationNetwork]; + node = [config objectForKey: OFAppleTalkInterfaceConfigurationNode]; + phase = [config objectForKey: OFAppleTalkInterfaceConfigurationPhase]; + range = [config + objectForKey: OFAppleTalkInterfaceConfigurationNetworkRange]; + + if (network == nil || node == nil) + @throw [OFInvalidArgumentException exception]; + + if (phase != nil && phase.unsignedCharValue != 1 && + phase.unsignedCharValue != 2) + @throw [OFInvalidArgumentException exception]; + +# ifdef OF_MACOS + if ((sock = socket(AF_APPLETALK, SOCK_RAW, 0)) < 0) +# else + if ((sock = socket(AF_APPLETALK, SOCK_DGRAM, 0)) < 0) +# endif + @throw [OFSetOptionFailedException + exceptionWithObject: nil + errNo: OFSocketErrNo()]; + + memset(&request, 0, sizeof(request)); + strncpy(request.ifr_name, interfaceName.UTF8String, IFNAMSIZ - 1); + sat = (struct sockaddr_at *)&request.ifr_addr; + sat->sat_family = AF_APPLETALK; + sat->sat_net = OFToBigEndian16(network.unsignedShortValue); + sat->sat_node = node.unsignedCharValue; + /* + * The netrange is hidden in sat_zero and different OSes use different + * struct names for it, so the portable way is setting sat_zero + * directly. + */ + sat->sat_zero[0] = (phase != nil ? phase.unsignedCharValue : 2); + if (range != nil) { + rangeStart = [range.firstObject unsignedShortValue]; + rangeEnd = [range.secondObject unsignedShortValue]; + } else { + rangeStart = rangeEnd = network.unsignedShortValue; + } + sat->sat_zero[2] = rangeStart >> 8; + sat->sat_zero[3] = rangeStart & 0xFF; + sat->sat_zero[4] = rangeEnd >> 8; + sat->sat_zero[5] = rangeEnd & 0xFF; + + if (ioctl(sock, SIOCSIFADDR, &request) != 0) + @throw [OFSetOptionFailedException + exceptionWithObject: nil + errNo: OFSocketErrNo()]; + + close(sock); +} + ++ (OFAppleTalkInterfaceConfiguration) + configurationForInterface: (OFString *)interfaceName +{ + int sock; + struct ifreq request; + struct sockaddr_at *sat; +# ifndef OF_LINUX + uint16_t rangeStart, rangeEnd; + OFPair *range; +# endif + + if (interfaceName.UTF8StringLength > IFNAMSIZ - 1) + @throw [OFOutOfRangeException exception]; + +# ifdef OF_MACOS + if ((sock = socket(AF_APPLETALK, SOCK_RAW, 0)) < 0) +# else + if ((sock = socket(AF_APPLETALK, SOCK_DGRAM, 0)) < 0) +# endif + @throw [OFGetOptionFailedException + exceptionWithObject: nil + errNo: OFSocketErrNo()]; + + memset(&request, 0, sizeof(request)); + strncpy(request.ifr_name, interfaceName.UTF8String, IFNAMSIZ - 1); + + if (ioctl(sock, SIOCGIFADDR, &request) < 0) { + int errNo = OFSocketErrNo(); + + /* No AppleTalk configured on this interface. */ + if (errNo == EADDRNOTAVAIL) { + close(sock); + return nil; + } + + @throw [OFGetOptionFailedException exceptionWithObject: nil + errNo: errNo]; + } + + sat = (struct sockaddr_at *)&request.ifr_addr; + + close(sock); + +# ifndef OF_LINUX + /* + * Linux currently doesn't fill out the phase or netrange. + * + * The netrange is hidden in sat_zero and different OSes use different + * struct names for it, so the portable way is setting sat_zero + * directly. + */ + rangeStart = sat->sat_zero[2] << 8 | sat->sat_zero[3]; + rangeEnd = sat->sat_zero[4] << 8 | sat->sat_zero[5]; + range = [OFPair + pairWithFirstObject: [OFNumber numberWithUnsignedShort: rangeStart] + secondObject: [OFNumber numberWithUnsignedShort: rangeEnd]]; +# endif + + return [OFDictionary dictionaryWithKeysAndObjects: + OFAppleTalkInterfaceConfigurationNode, + [OFNumber numberWithUnsignedChar: sat->sat_node], + OFAppleTalkInterfaceConfigurationNetwork, + [OFNumber numberWithUnsignedShort: OFFromBigEndian16(sat->sat_net)], +# ifndef OF_LINUX + OFAppleTalkInterfaceConfigurationPhase, + [OFNumber numberWithUnsignedChar: sat->sat_zero[0]], + OFAppleTalkInterfaceConfigurationNetworkRange, range, +# endif + nil]; +} + ++ (void)removeConfigurationForInterface: (OFString *)interfaceName +{ + int sock; + struct ifreq request; + + if (interfaceName.UTF8StringLength > IFNAMSIZ - 1) + @throw [OFOutOfRangeException exception]; + +# ifdef OF_MACOS + if ((sock = socket(AF_APPLETALK, SOCK_RAW, 0)) < 0) +# else + if ((sock = socket(AF_APPLETALK, SOCK_DGRAM, 0)) < 0) +# endif + @throw [OFSetOptionFailedException + exceptionWithObject: nil + errNo: OFSocketErrNo()]; + + memset(&request, 0, sizeof(request)); + strncpy(request.ifr_name, interfaceName.UTF8String, IFNAMSIZ - 1); + request.ifr_addr.sa_family = AF_APPLETALK; + + if (ioctl(sock, SIOCDIFADDR, &request) != 0) + @throw [OFSetOptionFailedException + exceptionWithObject: nil + errNo: OFSocketErrNo()]; +} +#endif - (OFSocketAddress)bindToNetwork: (uint16_t)network node: (uint8_t)node port: (uint8_t)port protocolType: (uint8_t)protocolType Index: src/OFSocket.h ================================================================== --- src/OFSocket.h +++ src/OFSocket.h @@ -59,12 +59,10 @@ # ifdef OF_HAVE_APPLETALK # include # endif #endif -/** @file */ - #ifdef OF_WII # include #endif #ifdef OF_PSP @@ -72,10 +70,12 @@ #endif #import "macros.h" OF_ASSUME_NONNULL_BEGIN + +/** @file */ #ifndef OF_WINDOWS typedef int OFSocketHandle; static const OFSocketHandle OFInvalidSocketHandle = -1; #else Index: src/exceptions/OFGetOptionFailedException.h ================================================================== --- src/exceptions/OFGetOptionFailedException.h +++ src/exceptions/OFGetOptionFailedException.h @@ -23,19 +23,19 @@ * * @brief An exception indicating that getting an option for an object failed. */ @interface OFGetOptionFailedException: OFException { - id _object; + id _Nullable _object; int _errNo; OF_RESERVE_IVARS(OFGetOptionFailedException, 4) } /** * @brief The object for which the option could not be retrieved. */ -@property (readonly, nonatomic) id object; +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) id object; /** * @brief The errno of the error that occurred. */ @property (readonly, nonatomic) int errNo; @@ -45,11 +45,11 @@ * * @param object The object for which the option could not be retrieved * @param errNo The errno of the error that occurred * @return A new, autoreleased get option failed exception */ -+ (instancetype)exceptionWithObject: (id)object errNo: (int)errNo; ++ (instancetype)exceptionWithObject: (nullable id)object errNo: (int)errNo; + (instancetype)exception OF_UNAVAILABLE; /** * @brief Initializes an already allocated get option failed exception. @@ -56,12 +56,12 @@ * * @param object The object for which the option could not be retrieved * @param errNo The errno of the error that occurred * @return An initialized get option failed exception */ -- (instancetype)initWithObject: (id)object +- (instancetype)initWithObject: (nullable id)object errNo: (int)errNo OF_DESIGNATED_INITIALIZER; - (instancetype)init OF_UNAVAILABLE; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFGetOptionFailedException.m ================================================================== --- src/exceptions/OFGetOptionFailedException.m +++ src/exceptions/OFGetOptionFailedException.m @@ -53,10 +53,14 @@ [super dealloc]; } - (OFString *)description { - return [OFString stringWithFormat: - @"Getting an option in an object of type %@ failed: %@", - [_object class], OFStrError(_errNo)]; + if (_object != nil) + return [OFString stringWithFormat: + @"Getting an option in an object of type %@ failed: %@", + [_object class], OFStrError(_errNo)]; + else + return [OFString stringWithFormat: + @"Getting an failed: %@", OFStrError(_errNo)]; } @end Index: src/exceptions/OFSetOptionFailedException.h ================================================================== --- src/exceptions/OFSetOptionFailedException.h +++ src/exceptions/OFSetOptionFailedException.h @@ -23,19 +23,19 @@ * * @brief An exception indicating that setting an option for an object failed. */ @interface OFSetOptionFailedException: OFException { - id _object; + id _Nullable _object; int _errNo; OF_RESERVE_IVARS(OFSetOptionFailedException, 4) } /** * @brief The object for which the option could not be set. */ -@property (readonly, nonatomic) id object; +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) id object; /** * @brief The errno of the error that occurred. */ @property (readonly, nonatomic) int errNo; @@ -45,11 +45,11 @@ * * @param object The object for which the option could not be set * @param errNo The errno of the error that occurred * @return A new, autoreleased set option failed exception */ -+ (instancetype)exceptionWithObject: (id)object errNo: (int)errNo; ++ (instancetype)exceptionWithObject: (nullable id)object errNo: (int)errNo; + (instancetype)exception OF_UNAVAILABLE; /** * @brief Initializes an already allocated set option failed exception. @@ -56,12 +56,12 @@ * * @param object The object for which the option could not be set * @param errNo The errno of the error that occurred * @return An initialized set option failed exception */ -- (instancetype)initWithObject: (id)object +- (instancetype)initWithObject: (nullable id)object errNo: (int)errNo OF_DESIGNATED_INITIALIZER; - (instancetype)init OF_UNAVAILABLE; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFSetOptionFailedException.m ================================================================== --- src/exceptions/OFSetOptionFailedException.m +++ src/exceptions/OFSetOptionFailedException.m @@ -53,10 +53,14 @@ [super dealloc]; } - (OFString *)description { - return [OFString stringWithFormat: - @"Setting an option in an object of type %@ failed: %@", - [_object class], OFStrError(_errNo)]; + if (_object != nil) + return [OFString stringWithFormat: + @"Setting an option in an object of type %@ failed: %@", + [_object class], OFStrError(_errNo)]; + else + return [OFString stringWithFormat: + @"Setting an option failed: %@", OFStrError(_errNo)]; } @end Index: src/objfw-defs.h.in ================================================================== --- src/objfw-defs.h.in +++ src/objfw-defs.h.in @@ -2,10 +2,11 @@ #undef OF_BIG_ENDIAN #undef OF_CLASSIC_MACOS #undef OF_FLOAT_BIG_ENDIAN #undef OF_HAVE_AFUNIX_H #undef OF_HAVE_APPLETALK +#undef OF_HAVE_APPLETALK_IFCONFIG #undef OF_HAVE_ATOMIC_BUILTINS #undef OF_HAVE_ATOMIC_OPS #undef OF_HAVE_BUILTIN_BSWAP16 #undef OF_HAVE_BUILTIN_BSWAP32 #undef OF_HAVE_BUILTIN_BSWAP64 Index: utils/ofatalkcfg/OFATalkCfg.m ================================================================== --- utils/ofatalkcfg/OFATalkCfg.m +++ utils/ofatalkcfg/OFATalkCfg.m @@ -14,96 +14,31 @@ */ #include "config.h" #include -#include "unistd.h" #import "OFApplication.h" #import "OFArray.h" +#import "OFDDPSocket.h" +#import "OFDictionary.h" +#import "OFNumber.h" #import "OFOptionsParser.h" -#import "OFSocket.h" +#import "OFPair.h" #import "OFStdIOStream.h" #import "OFInvalidFormatException.h" -#ifdef HAVE_NET_IF_H -# include -#endif -#ifdef HAVE_SYS_IOCTL_H -# include -#endif - @interface OFATalkCfg: OFObject @end OF_APPLICATION_DELEGATE(OFATalkCfg) -static void -configureInterface(OFString *interface, uint16_t network, uint8_t node, - uint8_t phase, uint16_t rangeStart, uint16_t rangeEnd) -{ - int sock; - struct ifreq request; - struct sockaddr_at *sat; - - if (interface.UTF8StringLength > IFNAMSIZ - 1) { - [OFStdErr writeFormat: @"%@: Interface name too long!\n", - [OFApplication programName]]; - [OFApplication terminateWithStatus: 1]; - } - -#ifdef OF_MACOS - if ((sock = socket(AF_APPLETALK, SOCK_RAW, 0)) < 0) { -#else - if ((sock = socket(AF_APPLETALK, SOCK_DGRAM, 0)) < 0) { -#endif - int errNo = OFSocketErrNo(); - - [OFStdErr writeFormat: @"%@: Failed to create socket: %@\n", - [OFApplication programName], - OFStrError(errNo)]; - -#ifdef OF_LINUX - if (errNo == EAFNOSUPPORT) - [OFStdErr writeLine: @"Did you forget to run " - @"\"modprobe appletalk\"?"]; -#endif - - [OFApplication terminateWithStatus: 1]; - } - - memset(&request, 0, sizeof(request)); - strncpy(request.ifr_name, interface.UTF8String, IFNAMSIZ - 1); - sat = (struct sockaddr_at *)&request.ifr_addr; - sat->sat_family = AF_APPLETALK; - sat->sat_net = OFToBigEndian16(network); - sat->sat_node = node; - /* - * The netrange is hidden in sat_zero and different OSes use different - * struct names for it, so the portable way is setting sat_zero - * directly. - */ - sat->sat_zero[0] = phase; - sat->sat_zero[2] = rangeStart >> 8; - sat->sat_zero[3] = rangeStart & 0xFF; - sat->sat_zero[4] = rangeEnd >> 8; - sat->sat_zero[5] = rangeEnd & 0xFF; - - if (ioctl(sock, SIOCSIFADDR, &request) != 0) { - [OFStdErr writeFormat: @"%@: Failed to set address: %@\n", - [OFApplication programName], - OFStrError(OFSocketErrNo())]; - [OFApplication terminateWithStatus: 1]; - } - - close(sock); -} - @implementation OFATalkCfg - (void)applicationDidFinishLaunching: (OFNotification *)notification { + OFMutableDictionary *config = [OFMutableDictionary dictionary]; OFString *nodeString = nil, *networkString = nil, *phaseString = nil; OFString *rangeString = nil; const OFOptionsParserOption options[] = { { '\0', @"network", 1, NULL, &networkString }, { '\0', @"node", 1, NULL, &nodeString }, @@ -178,10 +113,12 @@ if (network > UINT16_MAX) { [OFStdErr writeFormat: @"%@: --network out of range!\n", [OFApplication programName]]; [OFApplication terminateWithStatus: 1]; } + [config setObject: [OFNumber numberWithUnsignedShort: (uint16_t)network] + forKey: OFAppleTalkInterfaceConfigurationNetwork]; if (nodeString == nil) { [OFStdErr writeFormat: @"%@: --node not specified!\n", [OFApplication programName]]; [OFApplication terminateWithStatus: 1]; @@ -197,10 +134,12 @@ if (node > UINT8_MAX) { [OFStdErr writeFormat: @"%@: --node out of range!\n", [OFApplication programName]]; [OFApplication terminateWithStatus: 1]; } + [config setObject: [OFNumber numberWithUnsignedChar: (uint8_t)node] + forKey: OFAppleTalkInterfaceConfigurationNode]; if (phaseString != nil) { @try { phase = [phaseString unsignedLongLongValueWithBase: 0]; } @catch (OFInvalidFormatException *e) { @@ -214,14 +153,21 @@ if (phase > 2) { [OFStdErr writeFormat: @"%@: --phase out of range!\n", [OFApplication programName]]; [OFApplication terminateWithStatus: 1]; } - } else - phase = 2; + + [config setObject: [OFNumber + numberWithUnsignedChar: (uint8_t)phase] + forKey: OFAppleTalkInterfaceConfigurationPhase]; + } if (rangeString != nil) { + const OFAppleTalkInterfaceConfigurationKey key = + OFAppleTalkInterfaceConfigurationNetworkRange; + OFPair *range; + rangeArray = [rangeString componentsSeparatedByString: @"-"]; if (rangeArray.count != 2) { [OFStdErr writeFormat: @"%@: Invalid format for --range!\n", [OFApplication programName]]; @@ -244,17 +190,21 @@ if (rangeStart > UINT16_MAX || rangeEnd > UINT16_MAX) { [OFStdErr writeFormat: @"%@: --range out of range!\n", [OFApplication programName]]; [OFApplication terminateWithStatus: 1]; } - } else { - rangeStart = network; - rangeEnd = network; + + range = [OFPair + pairWithFirstObject: [OFNumber numberWithUnsignedShort: + (uint16_t)rangeStart] + secondObject: [OFNumber numberWithUnsignedShort: + (uint16_t)rangeEnd]]; + [config setObject: range forKey: key]; } - configureInterface(optionsParser.remainingArguments.firstObject, - (uint16_t)network, (uint8_t)node, (uint8_t)phase, - (uint16_t)rangeStart, (uint16_t)rangeEnd); + [OFDDPSocket setConfiguration: config + forInterface: optionsParser.remainingArguments + .firstObject]; [OFApplication terminate]; } @end