diff options
Diffstat (limited to 'tcl/mac')
47 files changed, 21488 insertions, 0 deletions
diff --git a/tcl/mac/AppleScript.html b/tcl/mac/AppleScript.html new file mode 100644 index 00000000000..4a73fbb384a --- /dev/null +++ b/tcl/mac/AppleScript.html @@ -0,0 +1,298 @@ +<HTML> + +<HEAD> + +<TITLE>tclOSAScript -- OSA</TITLE> + +</HEAD> + +<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#FF0000" ALINK="#00FF00"> + +<H2 ALIGN="CENTER">TclAppleScript Extension Command</H2> + +<H3>NAME</H3> +<DL> +<DT> +AppleScript - Communicate with the AppleScript OSA component to run + AppleScripts from Tcl. +</DL> +<H3>SYNOPSIS</H3> +<DL><DT> +<B>AppleScript <A NAME="compile">compile</A> </B><I>?-flag value?</I> <I>scriptData1 + ?ScriptData2 ...?</I><I>componentName</I> +<BR> +<B>AppleScript <A NAME="decompile">decompile</A></B> <I>scriptName</I> +<BR> +<B>AppleScript delete </B><I>scriptName</I> +<BR> +<B>AppleScript <A NAME="execute">execute</A> </B><I>?flags value?</I> <I>scriptData1 + ?scriptData2 ...?</I> +<BR> +<B>AppleScript <A NAME="info">info</A> </B><I>what</I> +<BR> +<B>AppleScript <A NAME="load">load</A></B> <I>?flag value? fileName</I> +<BR> +<B>AppleScript <A NAME="run">run</A></B> <I>?flag value?</I> + <I>scriptName</I> +<BR> +<B>AppleScript <A NAME="store">store</A></B> <I>?flag value? scriptName fileName</I> +<BR> +</DL> + +<H3>DESCRIPTION</H3> +<DL> +<DT> + + +This command is used to communicate with the AppleScript OSA component. +You can <A HREF="#compile"><B>compile</B></A> scripts, <A +HREF="#run"><B>run</B></A> compiled scripts, <A +HREF="#execute"><B>execute</B></A> script data (i.e. compile and run at a +blow). You can get script data from a compiled script (<A +HREF="#decompile"><B>decompile</B></A> it), and you can <A +HREF="#load"><B>load</B></A> a compiled script from the scpt resource of a +file, or <A HREF="store"><B>store</B></A> one to a scpt resource. You can +also get <A HREF="#info"><B>info</B></A> on the currently available scripts +and contexts. It has the general form + +<DL> +<DT> +<P> +<I>AppleScript option ?arg arg ...?</I> +<P> +</DL> +The possible sub-commands are: +<P> +<DL> + <DT> + <I>AppleScript</I> <A NAME="compile"><B>compile</A> </B><I>?-flag value?</I> <I>scriptData1 + ?ScriptData2 ...?</I> + <BR> + + <DD> + The scriptData + elements are concatenated (with a space between each), and + sent to AppleScript + for compilation. There is no limitation on the size of + the scriptData, beyond the available memory of the Wish interpreter. + <P> + If the compilation is successful, then the command will return a token + that you can pass to the <A HREF="#run">"run"</A> subcommand. If the + compilation fails, then the return value will be the error message from + AppleScript, and the pertinent line of code, with an "_" to indicate + the place where it thinks the error occured. + <P> + The + compilation is controlled by flag value pairs. The available flags + are: + <P> + <DL> + <DT> + <A NAME="first compile switch"><B>-augment Boolean</B></A> + <DD> + To be used in concert with the <A HREF="#-context">-context</A> flag. + If augment is yes, + then the scriptData augments the handlers and data already in the + script context. If augment is no, then the scriptData replaces the + data and handlers already in the context. The default is yes. + <P> + <!-- I'm leaving this flag out for now, since I can't seem to get the + AE manager to obey it. Even when I hard code the value, applications + still switch to the foreground. Oh, well... + + <DT> + <B>-canswitch Boolean </B> + <DD> + If yes, then applications activated by the code in scriptData will + be allowed to switch to the foreground. If no, then they will use + the notification manager to indicate they need attention (this + usually means they blink the Finder icon, and put a check in the + application's entry in the Finder menu). + --> + + <DT> + <B><A NAME="-context">-context</A> Boolean</B> + <DD> + This flag causes the code given in the scriptData to be compiled + into a "context". In AppleScript, this is the equivalent of creating an Tcl + Namespace. The command in this case returns the name of the context as + the its result, rather than a compiled script name. + <P> + You can store data and procedures (aka + handlers) in a script context. Then later, you can + run other scripts in this context, and they will see all the data and + handlers that were set up with this command. You do this by passing the + name of this context to the -context flag of the run or execute subcommands. + <P> + Unlike the straight compile command, the code compiled into a + script context is run immediatly, when it is compiled, to set up the context. + <DT> + <P> + <B>-name string</B> + <DD> + Use <I>string</I> as the name of the script or script context. If there is + already a script + of this name, it will be discarded. The same is true with script + contexts, unless the <I>-augment</I> flag is true. If no name is provided, then a + unique name will be created for you. + <DT> + <P> + <B>-parent contextName </B> + <DD> + This flag is also to be used in conjunction with the <A HREF="#-context">-context</A> flag. + <I>contextName</I> must be the name of a compiled script context. Then + the new script context will inherit the data and handlers from the + parent context. + </DL> + <P> + <DT> + <I>AppleScript</I> <B><A NAME="decompile">decompile</A></B> <I>scriptName</I> + <BR> + <DD> + This decompiles the script data compiled into the script scriptName, + and returns the source code. + <P> + <DT> + <I>AppleScript</I> <B>delete </B><I>scriptName</I> + <BR> + <DD> + This deletes the script data compiled into the script scriptName, + and frees up all the resources associated with it. + <P> + <DT> + <I>AppleScript</I> <B><A NAME="execute">execute</A> </B><I>?flags value?</I> <I>scriptData1 + ?scriptData2 ...?</I> + <BR> + <DD> + This compiles and runs the script in scriptData (concatenating first), and + returns the results of the script execution. It is the same as doing + <I>compile</I> and then <I>run</I>, except that the compiled script is + immediately discarded. + <P> + <DT> + <I>AppleScript</I> <B><A NAME="info">info</A> </B><I>what</I> + <DD> + This gives info on the connection. The allowed values for "what" are: + <P> + <DL> + <DT> + <P> + <B>contexts </B> <I>?pattern?</I> + <DD> + This gives the list of the script contexts that have been. + If <I>pattern</I> is given, it only reports the contexts + that match this pattern. + <DT> + <!-- <P> + <B>language</B> + <DD> + Returns the language of this OSA component + <DT> + --> + <P> + <B>scripts</B> <I>?pattern?</I> + <DD> + This returns a list of the scripts that have been compiled in the + current connection. If <I>pattern</I> is given, it only reports the + script names that match this pattern. + </DL> + <P> + <DT> + <I>AppleScript</I> <B><A NAME="load">load</A></B> <I>?flag value? fileName</I> + <DD> + This loads compiled script data from a resource of type 'scpt' in the + file fileName, and returns a token for the script data. As with the + <I>compile</I> command, the script is not actually executed. Note that all + scripts compiled with Apple's "Script Editor" are stored as script + contexts. However, unlike with the "<I>compile -context</I>" command, the <I>load</I> + command does not run these scripts automatically. If you want to set up + the handlers contained in the loaded script, you must run it manually. + <P> + <I>load</I> takes the following flags: + <P> + <DL> + <DT> + <B>-rsrcname string</B> + <DD> + load a named resource of type 'scpt' using the rsrcname + flag. + <DT> + <P> + <B>-rsrcid integer</B> + <DD> + load a resource by number with the rsrcid flag. + </DL> + <DD> + <P> + If neither the <I>rsrcname</I> nor the <I>rsrcid</I> flag is provided, then the load + command defaults to -rsrcid = 128. This is the resource in which + Apple's Script Editor puts the script data when it writes out a + compiled script. + <P> + <DT> + <I>AppleScript</I> <B><A NAME="run">run</A></B> <I>?flag value?</I> <I>scriptName</I> + <DD> + This runs the script which was previously compiled into <I>scriptName</I>. If the script + runs successfully, the command returns the return value for this command, + coerced to a text string. + If there is an error in + the script execution, then it returns the error result from the + scripting component. It accepts the following flag: + + <DL> + <DT> + <P> + <B>-context contextName</B> + <DD> + <I>contextName</I> must be a context created by a previous call to <I>compile</I> with + the -<I>context</I> flag set. This flag causes the code given in the + <I>scriptData</I> to be run in this "context". It will see all the data and + handlers that were set up previously. + <!-- <DT> + <B>-canswitch Boolean </B> + <DD> + If yes, then applications activated by the code + in scriptData will be allowed to switch to the foreground. If no, then + they will use the notification manager to indicate they need attention + (this usually means they blink the Finder icon, and put a check in the + application's entry in the Finder menu). --> + </DL> + <P> + <DT> + <I>AppleScript </I> <B> <A NAME="store">store</A></B> <I>?flag value? scriptName fileName</I> + <DD> + This stores a compiled script or script context into a resource of type 'scpt' in the + file fileName. + <P> + store takes the following flags: + <P> + <DL> + <DT> + <B>-rsrcname string</B> + <DD> + store to a named resource of type 'scpt' using the rsrcname + flag. + <DT> + <P> + <B>-rsrcid integer</B> + <DD> + store to a numbered resource with the rsrcid flag. + </DL> + <P> + <DD> + If neither the rsrcname nor the rsrcid flag is provided, then the load + command defaults to -rsrcid = 128. Apple's Script Editor can read in files written by + tclOSAScript with this setting of the <I>-rsrcid</I> flag. +</DL> +</DL> +<H2>Notes:</H2> + +The AppleScript command is a stopgap command to fill the place of exec + on the Mac. It is not a supported command, and will likely change + as we broaden it to allow communication with other OSA languages. +<H2>See Also:</H2> + + +</BODY> + +</HTML> diff --git a/tcl/mac/Background.doc b/tcl/mac/Background.doc new file mode 100644 index 00000000000..b28e79e16d4 --- /dev/null +++ b/tcl/mac/Background.doc @@ -0,0 +1,92 @@ +Notes about the Background Only application template +==================================================== + +RCS: @(#) $Id$ + +We have included sample code and project files for making a Background-Only + application (BOA) in Tcl. This could be used for server processes (like the +Tcl Web-Server). + +Files: +------ + +* BOA_TclShells.¼ - This is the project file. +* tclMacBOAAppInit.c - This is the AppInit file for the BOA App. +* tclMacBOAMain - This is a replacement for the Tcl_Main for BOA's. + +Caveat: +------- + +This is an unsupported addition to MacTcl. The main feature that will certainly +change is how we handle AppleEvents. Currently, all the AppleEvent handling is +done on the Tk side, which is not really right. Also, there is no way to +register your own AppleEvent handlers, which is obviously something that would be +useful in a BOA App. We will address these issues in Tcl8.1. If you need to +register your own AppleEvent Handlers in the meantime, be aware that your code +will probably break in Tcl8.1. + +I will also improve the basic code here based on feedback that I recieve. This +is to be considered a first cut only at writing a BOA in Tcl. + +Introduction: +------------- + +This project makes a double-clickable BOA application. It obviously needs +some Tcl code to get it started. It will look for this code first in a +'TEXT' resource in the application shell whose name is "bgScript.tcl". If +it does not find any such resource, it will look for a file called +bgScript.tcl in the application's folder. Otherwise it will quit with an +error. + +It creates three files in the application folder to store stdin, stdout & +stderr. They are imaginatively called temp.in, temp.out & temp.err. They +will be opened append, so you do not need to erase them after each use of +the BOA. + +The app does understand the "quit", and the "doScript" AppleEvents, so you can +kill it with the former, and instruct it with the latter. It also has an +aete, so you can target it with Apple's "Script Editor". + +For more information on Macintosh BOA's, see the Apple TechNote: 1070. + +Notifications: +-------------- + +BOA's are not supposed to have direct contact with the outside world. They +are, however, allowed to go through the Notification Manager to post +alerts. To this end, I have added a Tcl command called "bgnotify" to the +shell, that simply posts a notification through the notification manager. + +To use it, say: + +bgnotify "Hi, there little buddy" + +It will make the system beep, and pop up an annoying message box with the +text of the first argument to the command. While the message is up, Tcl +is yielding processor time, but not processing any events. + +Errors: +------- + +Usually a Tcl background application will have some startup code, opening +up a server socket, or whatever, and at the end of this, will use the +vwait command to kick off the event loop. If an error occurs in the +startup code, it will kill the application, and a notification of the error +will be posted through the Notification Manager. + +If an error occurs in the event handling code after the +vwait, the error message will be written to the file temp.err. However, +if you would like to have these errors post a notification as well, just +define a proc called bgerror that takes one argument, the error message, +and passes that off to "bgnotify", thusly: + +proc bgerror {mssg} { + bgnotify "A background error has occured\n $mssg" +} + +Support: +-------- + +If you have any questions, contact me at: + +jim.ingham@eng.sun.com diff --git a/tcl/mac/MW_TclAppleScriptHeader.pch b/tcl/mac/MW_TclAppleScriptHeader.pch new file mode 100644 index 00000000000..afd6cdba1ea --- /dev/null +++ b/tcl/mac/MW_TclAppleScriptHeader.pch @@ -0,0 +1,47 @@ +/* + * MW_TclAppleScriptHeader.pch -- + * + * This file is the source for a pre-compilied header that gets used + * for TclAppleScript. This make compilies go a bit + * faster. This file is only intended to be used in the MetroWerks + * CodeWarrior environment. It essentially acts as a place to set + * compiler flags. See MetroWerks documention for more details. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +/* + * To use the compilied header you need to set the "Prefix file" in + * the "C/C++ Language" preference panel to point to the created + * compilied header. The name of the header depends on the + * architecture we are compiling for (see the code below). For + * example, for a 68k app the prefix file should be: MW_TclHeader68K. + */ + +#if __POWERPC__ +#pragma precompile_target "MW_TclAppleScriptHeaderPPC" +#include "MW_TclHeaderPPC" +#elif __CFM68K__ +#pragma precompile_target "MW_TclAppleScriptHeaderCFM68K" +#include "MW_TclHeaderCFM68K" +#else +#pragma precompile_target "MW_TclAppleScriptHeader68K" +#include "MW_TclHeader68K" +#endif + + +#define TCL_REGISTER_LIBRARY 1 +/* + * Place any includes below that will are needed by the majority of the + * and is OK to be in any file in the system. The pragma's are used + * to control what functions are exported in the Tcl shared library. + */ + +#pragma export on +#pragma export off + diff --git a/tcl/mac/MW_TclHeader.pch b/tcl/mac/MW_TclHeader.pch new file mode 100644 index 00000000000..bca9fe42366 --- /dev/null +++ b/tcl/mac/MW_TclHeader.pch @@ -0,0 +1,47 @@ +/* + * MW_TclHeader.pch -- + * + * This file is the source for a pre-compilied header that gets used + * for all files in the Tcl projects. This make compilies go a bit + * faster. This file is only intended to be used in the MetroWerks + * CodeWarrior environment. It essentially acts as a place to set + * compiler flags. See MetroWerks documention for more details. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +/* + * To use the compilied header you need to set the "Prefix file" in + * the "C/C++ Language" preference panel to point to the created + * compilied header. The name of the header depends on the + * architecture we are compiling for (see the code below). For + * example, for a 68k app the prefix file should be: MW_TclHeader68K. + */ +#if __POWERPC__ +#pragma precompile_target "MW_TclHeaderPPC" +#elif __CFM68K__ +#pragma precompile_target "MW_TclHeaderCFM68K" +#else +#pragma precompile_target "MW_TclHeader68K" +#endif + +#include "tclMacCommonPch.h" + +/* + * Place any includes below that will are needed by the majority of the + * and is OK to be in any file in the system. The pragma's are used + * to control what functions are exported in the Tcl shared library. + */ + +#pragma export on +#include "tcl.h" +#include "tclMac.h" +#include "tclInt.h" + +#pragma export reset + diff --git a/tcl/mac/README b/tcl/mac/README new file mode 100644 index 00000000000..b8ca40e17c1 --- /dev/null +++ b/tcl/mac/README @@ -0,0 +1,195 @@ +Tcl 8.0.3 for Macintosh + +by Ray Johnson +Scriptics Corporation +rjohnson@scriptics.com +with major help from +Jim Ingham +Cygnus Solutions +jingham@cygnus.com + +RCS: @(#) $Id$ + +1. Introduction +--------------- + +This is the README file for the Macintosh version of the Tcl +scripting language. The file consists of information specific +to the Macintosh version of Tcl. For more general information +please read the README file in the main Tcl directory. + +2. What's new? +-------------- + +The main new feature is the Tcl compilier. You should certainly +notice the speed improvements. Any problems are probably +generic rather than Mac specific. If you have questions or +comments about the compilier feel free to forward them to the +author of the compilier: Brian Lewis <btlewis@eng.sun.com>. +Several things were fixed/changed since the a1 release so be +sure to check this out. + +The largest incompatible change on the Mac is the removal of the +following commands: "rm", "rmdir", "mkdir", "mv" and "cp". These +commands were never really supported and their functionality is +superceded by the file command. + +I've also added in a new "AppleScript" command. This was contributed +by Jim Ingham who is a new member of the Tcl group. It's very cool. +The command isn't actually in the core - you need to do a "package +require Tclapplescript" to get access to it. This code is officially +unsupported and will change in the next release. However, the core +functionality is there and is stable enough to use. Documentation +can be found in "AppleScript.html" in the mac subdirectory. + +The resource command has also been rewacked. You can now read and +write any Mac resource. Tcl now has the new (and VERY COOL) binary +command that will allow you to pack and unpack the resources into +useful Tcl code. We will eventually provide Tcl libraries for +accessing the most common resources. + +See the main Tcl README for other features new to Tcl 8.0. + +3. Mac specific features +------------------------ + +There are several features or enhancements in Tcl that are unique to +the Macintosh version of Tcl. Here is a list of those features and +pointers to where you can find more information about the feature. + +* The "resource" command allows you manipulate Macintosh resources. + A complete man page is available for this command. + +* The Mac version of the "source" command has an option to source from + a Macintosh resource. Check the man page from the source command + for details. + +* The only command NOT available on the Mac is the exec command. + However, we include a Mac only package called Tclapplescript that + provides access to Mac's AppleScript system. This command is still + under design & construction. Documentatin can be found in the mac + subdirectory in a file called "AppleScript.html". + +* The env variable on the Macintosh works rather differently than on + Windows or UNIX platforms. Check out the tclvars man page for + details. + +* The command "file volumes" returns the available volumes on your + Macintosh. Check out the file command for details. + +* The command "file attributes" has the Mac specific options of + -creator and -type which allow you to query and set the Macintosh + creator and type codes for Mac files. See file man page for details. + +* We have added a template for creating a Background-only Tcl application. + So you can use Tcl as a faceless server process. For more details, see + the file background.doc. + +If you are writing cross platform code but would still like to use +some of these Mac specific commands, please remember to use the +tcl_platform variable to special case your code. + +4. The Distribution +------------------- + +Macintosh Tcl is distributed in three different forms. This +should make it easier to only download what you need. The +packages are as follows: + +mactk8.0.3.sea.hqx + + This distribution is a "binary" only release. It contains an + installer program that will install a 68k, PowerPC, or Fat + version of the "Tcl Shell" and "Wish" applications. In addition, + it installs the Tcl & Tk libraries in the Extensions folder inside + your System Folder. + +mactcltk-full-8.0.3.sea.hqx + + This release contains the full release of Tcl and Tk for the + Macintosh plus the More Files packages which Macintosh Tcl and Tk + rely on. + +mactcl-source-8.0.3.sea.hqx + + This release contains the complete source for Tcl 8.0. In + addition, Metrowerks CodeWarrior libraries and project files + are included. However, you must already have the More Files + package to compile this code. + +5. Documentation +---------------- + +The "html" subdirectory contains reference documentation in +in the HTML format. You may also find these pages at: + + http://www.scriptics.com/man/tcl8.0/contents.html + +Other documentation and sample Tcl scripts can be found at +the Tcl archive site: + + ftp://ftp.neosoft.com/tcl/ + +and the Tcl resource center: + + http://www.scriptics.com/resource/ + +The internet news group comp.lang.tcl is also a valuable +source of information about Tcl. A mailing list is also +available (see below). + +6. Compiling Tcl +---------------- + +In order to compile Macintosh Tcl you must have the +following items: + + CodeWarrior Pro 2 or 3 + Mac Tcl 8.0 (source) + More Files 1.4.3 + +There are two sets of project files included with the package. The ones +we use for the release are for CodeWarrior Pro 3, and are not compatible +with CodeWarrior Gold release 11 and earlier. We have included the files +for earlier versions of CodeWarrior in the folder tcl8.0:mac:CW11 Projects, +but they are unsupported, and a little out of date. + +As of Tcl8.0p2, the code will also build under CW Pro 2. The only +change that needs to be made is that float.mac.c should be replaced by +float.c in the MacTcl MSL project file. + +However, there seems to be a bug in the CFM68K Linker in CW Pro 2, +which renders the CFM68K Version under CW Pro 2 very unstable. I am +working with MetroWerks to resolve this issue. The PPC version is +fine, as is the Traditional 68K Shell. But if you need to use the +CFM68K, then you must stay with CW Pro 1 for now. + +The project files included with the Mac Tcl source should work +fine. The only thing you may need to update are the access paths. +Unfortunantly, it's somewhat common for the project files to become +slightly corrupted. The most common problem is that the "Prefix file" +found in the "C/C++ Preference" panel is incorrect. This should be +set to MW_TclHeaderPPC, MW_TclHeader68K or MW_TclHeaderCFM68K. + +To build the fat version of TclShell, open the project file "TclShells.¼", +select the "TclShell" target, and build. All of the associated binaries will +be built automoatically. There are also targets for building static 68K +and Power PC builds, for building a CFM 68K build, and for building a +shared library Power PC only build. + +Special notes: + +* There is a small bug in More Files 1.4.3. Also you should not use + MoreFiles 1.4.4 - 1.4.6. Look in the file named morefiles.doc for + more details. + +* You may not have the libmoto library which will cause a compile + error. You don't REALLY need it - it can be removed. Look at the + file libmoto.doc for more details. + +* Check out the file bugs.doc for information about known bugs. + +If you have comments or Bug reports send them to: +Jim Ingham +jingham@cygnus.com + diff --git a/tcl/mac/bugs.doc b/tcl/mac/bugs.doc new file mode 100644 index 00000000000..114e0d9ab47 --- /dev/null +++ b/tcl/mac/bugs.doc @@ -0,0 +1,32 @@ +Known bug list for Tcl 8.0 for Macintosh + +by Ray Johnson +Sun Microsystems Laboratories +rjohnson@eng.sun.com + +RCS: @(#) $Id$ + +This was a new feature as of Tcl7.6b1 and as such I'll started with +a clean slate. I currently know of no reproducable bugs. I often +get vague reports - but nothing I've been able to confirm. Let +me know what bugs you find! + +The Macintosh version of Tcl passes most all tests in the Tcl +test suite. Slower Macs may fail some tests in event.test whose +timing constraints are too tight. If other tests fail please report +them. + +Ray + +Known bugs in the current release. + +* With the socket code you can't use the "localhost" host name. This + is actually a known bug in Apple's MacTcp stack. However, you can + use [info hostname] whereever you would have used "localhost" to + achive the same effect. + +* Most socket bugs have been fixed. We do have a couple of test cases + that will hang the Mac, however, and we are still working on them. + If you find additional test cases that show crashes please let us + know! + diff --git a/tcl/mac/libmoto.doc b/tcl/mac/libmoto.doc new file mode 100644 index 00000000000..ba84ba29ec5 --- /dev/null +++ b/tcl/mac/libmoto.doc @@ -0,0 +1,39 @@ +Notes about the use of libmoto +------------------------------ + +RCS: @(#) $Id$ + +First of all, libmoto is not required! If you don't have it, you +can simply remove the library reference from the project file and +everything should compile just fine. + +The libmoto library replaces certain functions in the MathLib and +ANSI libraries. Motorola has optimized the functions in the library +to run very fast on the PowerPC. As I said above, you don't need +this library, but it does make things faster. + +Obtaining Libmoto: + + For more information about Libmoto and how to doanload + it, visit the following URL: + + http://www.mot.com/SPS/PowerPC/library/fact_sheet/libmoto.html + + You will need to register for the library. However, the + library is free and you can use it in any commercial product + you might have. + +Installing Libmoto: + + Just follow the instructions provided by the Motorola + README file. You need to make sure that the Libmoto + library is before the ANSI and MathLib libraries in + link order. Also, you will get several warnings stateing + that certain functions have already been defined in + Libmoto. (These can safely be ignored.) + +Finally, you can thank Kate Stewart of Motorola for twisting my +arm at the Tcl/Tk Conference to provide some support for Libmoto. + +Ray Johnson + diff --git a/tcl/mac/license.terms b/tcl/mac/license.terms new file mode 100644 index 00000000000..9df3e600352 --- /dev/null +++ b/tcl/mac/license.terms @@ -0,0 +1,39 @@ +This software is copyrighted by the Regents of the University of +California, Sun Microsystems, Inc., Scriptics Corporation, +and other parties. The following terms apply to all files associated +with the software unless explicitly disclaimed in individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. diff --git a/tcl/mac/morefiles.doc b/tcl/mac/morefiles.doc new file mode 100644 index 00000000000..88329c503a2 --- /dev/null +++ b/tcl/mac/morefiles.doc @@ -0,0 +1,74 @@ +Notes about MoreFiles, dnr.c & other non-Tcl source files +--------------------------------------------------------- + +RCS: @(#) $Id$ + +The Macintosh distribution uses several source files that don't +actually ship with Tcl. This sometimes causes problems or confusion +to developers. This document should help clear up a few things. + +dnr.c +----- + +We have found a way to work around some bugs in dnr.c that +Apple has never fixed even though we sent in numerous bug reports. +The file tclMacDNR.c simply set's some #pragma's and the includes +the Apple dnr.c file. This should work the problems that many of +you have reported with dnr.c. + +More Files +---------- + +Macintosh Tcl/Tk also uses Jim Luther's very useful package called +More Files. More Files fixes many of the broken or underfunctional +parts of the file system. + +More Files can be found on the MetroWerks CD and Developer CD from +Apple. You can also down load the latest version from: + + ftp://members.aol.com/JumpLong/ + +The package can also be found at the home of Tcl/Tk for the mac: + + ftp://ftp.sunlabs.com/pub/tcl/mac/ + +I used to just link the More Files library in the Tcl projects. +However, this caused problems when libraries wern't matched correctly. +I'm now including the files in the Tcl project directly. This +solves the problem of missmatched libraries - but may not always +compile. + +If you get a compiliation error in MoreFiles you need to contact +Jim Luther. His email address: + + JumpLong@aol.com + +The version of More Files that we use with Tcl/Tk is 1.4.3. Early +version may work as well.. + +Unfortunantly, there is one bug in his library (in 1.4.3). The bug is +in the function FSpGetFullPath found in the file FullPath.c. After +the call to PBGetCatInfoSync you need to change the line: + + if ( result == noErr ) + + to: + + if ( (result == noErr) || (result == fnfErr) ) + + +The latest version of More Files is 1.4.6. Unfortunantly, this +version has a bug that keeps it from working with shared libraries +right out of the box. If you want to use 1.4.6 you can but you will +need to make the following fix: + + In the file "Opimization.h" in the More Files package you + need to remove the line "#pragma internal on". And in the + file "OptimazationEnd.h" you need to remove the line + "#pragma internal reset". + +Note: the version of MoreFile downloaded from the Sun Tcl/Tk site +will have the fix included. (If you want you can send email to +Jim Luther suggesting that he use Tcl for regression testing!) + +Ray Johnson diff --git a/tcl/mac/porting.notes b/tcl/mac/porting.notes new file mode 100644 index 00000000000..c15beb14245 --- /dev/null +++ b/tcl/mac/porting.notes @@ -0,0 +1,23 @@ +Porting Notes +------------- + +RCS: @(#) $Id$ + +Currently, the Macintosh version Tcl only compilies with the +CodeWarrior C compilier from MetroWerks. It should be straight +forward to port the Tcl source to MPW. + +Tcl on the Mac no longer requires the use of GUSI. It should now +be easier to port Tcl/Tk to other compiliers such as Symantic C +and MPW C. + +If you attempt to port Tcl to other Macintosh compiliers please +let me know. I would be glad to help with advice and encouragement. +If your efforts are succesfull I wold also be interested in puting +those changes into the core distribution. Furthermore, please feel +free to send me any notes you might make about your porting +experience so I may include them in this file for others to reference. + +Ray Johnson +ray.johnson@eng.sun.com + diff --git a/tcl/mac/tclMac.h b/tcl/mac/tclMac.h new file mode 100644 index 00000000000..3f121c5ca61 --- /dev/null +++ b/tcl/mac/tclMac.h @@ -0,0 +1,101 @@ +/* + * tclMac.h -- + * + * Declarations of Macintosh specific public variables and procedures. + * + * Copyright (c) 1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#ifndef _TCLMAC +#define _TCLMAC + +#ifndef _TCL +# include "tcl.h" +#endif +#include <Types.h> +#include <Files.h> +#include <Events.h> + +/* + * "export" is a MetroWerks specific pragma. It flags the linker that + * any symbols that are defined when this pragma is on will be exported + * to shared libraries that link with this library. + */ + +#pragma export on + +typedef int (*Tcl_MacConvertEventPtr) _ANSI_ARGS_((EventRecord *eventPtr)); + +/* + * This is needed by the shells to handle Macintosh events. + */ + +EXTERN void Tcl_MacSetEventProc _ANSI_ARGS_((Tcl_MacConvertEventPtr procPtr)); + +/* + * These routines are useful for handling using scripts from resources + * in the application shell + */ + +EXTERN char * Tcl_MacConvertTextResource _ANSI_ARGS_((Handle resource)); +EXTERN int Tcl_MacEvalResource _ANSI_ARGS_((Tcl_Interp *interp, + char *resourceName, int resourceNumber, char *fileName)); +EXTERN Handle Tcl_MacFindResource _ANSI_ARGS_((Tcl_Interp *interp, + long resourceType, char *resourceName, + int resourceNumber, char *resFileRef, int * releaseIt)); + +/* These routines support the new OSType object type (i.e. the packed 4 + * character type and creator codes). + */ + +EXTERN int Tcl_GetOSTypeFromObj _ANSI_ARGS_((Tcl_Interp *interp, + Tcl_Obj *objPtr, OSType *osTypePtr)); +EXTERN void Tcl_SetOSTypeObj _ANSI_ARGS_((Tcl_Obj *objPtr, + OSType osType)); +EXTERN Tcl_Obj * Tcl_NewOSTypeObj _ANSI_ARGS_((OSType osType)); + + + +/* + * The following routines are utility functions in Tcl. They are exported + * here because they are needed in Tk. They are not officially supported, + * however. The first set are from the MoreFiles package. + */ + +EXTERN pascal OSErr FSpGetDirectoryID(const FSSpec *spec, + long *theDirID, Boolean *isDirectory); +EXTERN pascal short FSpOpenResFileCompat(const FSSpec *spec, + SignedByte permission); +EXTERN pascal void FSpCreateResFileCompat(const FSSpec *spec, + OSType creator, OSType fileType, + ScriptCode scriptTag); +/* + * Like the MoreFiles routines these fix problems in the standard + * Mac calls. These routines is from tclMacUtils.h. + */ + +EXTERN int FSpLocationFromPath _ANSI_ARGS_((int length, CONST char *path, + FSSpecPtr theSpec)); +EXTERN OSErr FSpPathFromLocation _ANSI_ARGS_((FSSpecPtr theSpec, + int *length, Handle *fullPath)); + +/* + * These are not in MSL 2.1.2, so we need to export them from the + * Tcl shared library. They are found in the compat directory + * except the panic routine which is found in tclMacPanic.h. + */ + +EXTERN int strncasecmp _ANSI_ARGS_((CONST char *s1, + CONST char *s2, size_t n)); +EXTERN int strcasecmp _ANSI_ARGS_((CONST char *s1, + CONST char *s2)); +EXTERN void panic _ANSI_ARGS_(TCL_VARARGS(char *,format)); + +#pragma export reset + +#endif /* _TCLMAC */ diff --git a/tcl/mac/tclMacAETE.r b/tcl/mac/tclMacAETE.r new file mode 100644 index 00000000000..2090ff6adb8 --- /dev/null +++ b/tcl/mac/tclMacAETE.r @@ -0,0 +1,58 @@ +/* + * tclMacAETE.r -- + * + * This file creates the Apple Event Terminology resources + * for use Tcl and Tk. It is not used in the Simple Tcl shell + * since SIOUX does not support AppleEvents. An example of its + * use in Tcl is the TclBGOnly project. And it is used in all the + * Tk Shells. + * + * Copyright (c) 1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#define SystemSevenOrLater 1 + +#include <Types.r> +#include <SysTypes.r> +#include <AEUserTermTypes.r> + +/* + * The following resources defines the Apple Events that Tk can be + * sent from Apple Script. + */ + +resource 'aete' (0, "Wish Suite") { + 0x01, 0x00, english, roman, + { + "Required Suite", + "Events that every application should support", + 'reqd', 1, 1, + {}, + {}, + {}, + {}, + + "Wish Suite", "Events for the Wish application", 'WIsH', 1, 1, + { + "do script", "Execute a Tcl script", 'misc', 'dosc', + 'TEXT', "Result", replyOptional, singleItem, + notEnumerated, reserved, reserved, reserved, reserved, + reserved, reserved, reserved, reserved, reserved, + reserved, reserved, reserved, reserved, + 'TEXT', "Script to execute", directParamRequired, + singleItem, notEnumerated, changesState, reserved, + reserved, reserved, reserved, reserved, reserved, + reserved, reserved, reserved, reserved, reserved, + reserved, + {}, + }, + {}, + {}, + {}, + } +}; diff --git a/tcl/mac/tclMacAlloc.c b/tcl/mac/tclMacAlloc.c new file mode 100644 index 00000000000..71b6fb4effd --- /dev/null +++ b/tcl/mac/tclMacAlloc.c @@ -0,0 +1,348 @@ +/* + * tclMacAlloc.c -- + * + * This is a very fast storage allocator. It allocates blocks of a + * small number of different sizes, and keeps free lists of each size. + * Blocks that don't exactly fit are passed up to the next larger size. + * Blocks over a certain size are directly allocated by calling NewPtr. + * + * Copyright (c) 1983 Regents of the University of California. + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * Portions contributed by Chris Kingsley, Jack Jansen and Ray Johnson + *. + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tclMacInt.h" +#include "tclInt.h" +#include <Memory.h> +#include <stdlib.h> +#include <string.h> + + +/* + * Flags that are used by ConfigureMemory to define how the allocator + * should work. They can be or'd together. + */ +#define MEMORY_ALL_SYS 1 /* All memory should come from the system +heap. */ + +/* + * Amount of space to leave in the application heap for the Toolbox to work. + */ + +#define TOOLBOX_SPACE (32 * 1024) + +static int memoryFlags = 0; +static Handle toolGuardHandle = NULL; + /* This handle must be around so that we don't + * have NewGWorld failures. This handle is + * purgeable. Before we allocate any blocks, + * we see if this handle is still around. + * If it is not, then we try to get it again. + * If we can get it, we lock it and try + * to do the normal allocation, unlocking on + * the way out. If we can't, we go to the + * system heap directly. */ + + +/* + * The following typedef and variable are used to keep track of memory + * blocks that are allocated directly from the System Heap. These chunks + * of memory must always be freed - even if we crash. + */ + +typedef struct listEl { + Handle memoryHandle; + struct listEl * next; +} ListEl; + +ListEl * systemMemory = NULL; +ListEl * appMemory = NULL; + +/* + * Prototypes for functions used only in this file. + */ + +static pascal void CleanUpExitProc _ANSI_ARGS_((void)); +void ConfigureMemory _ANSI_ARGS_((int flags)); +void FreeAllMemory _ANSI_ARGS_((void)); + +/* + *---------------------------------------------------------------------- + * + * TclpSysRealloc -- + * + * This function reallocates a chunk of system memory. If the + * chunk is already big enough to hold the new block, then no + * allocation happens. + * + * Results: + * Returns a pointer to the newly allocated block. + * + * Side effects: + * May copy the contents of the original block to the new block + * and deallocate the original block. + * + *---------------------------------------------------------------------- + */ + +VOID * +TclpSysRealloc( + VOID *oldPtr, /* Original block */ + unsigned int size) /* New size of block. */ +{ + Handle hand; + void *newPtr; + int maxsize; + + hand = * (Handle *) ((Ptr) oldPtr - sizeof(Handle)); + maxsize = GetHandleSize(hand) - sizeof(Handle); + if (maxsize < size) { + newPtr = TclpSysAlloc(size, 1); + memcpy(newPtr, oldPtr, maxsize); + TclpSysFree(oldPtr); + } else { + newPtr = oldPtr; + } + return newPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TclpSysAlloc -- + * + * Allocate a new block of memory free from the System. + * + * Results: + * Returns a pointer to a new block of memory. + * + * Side effects: + * May obtain memory from app or sys space. Info is added to + * overhead lists etc. + * + *---------------------------------------------------------------------- + */ + +VOID * +TclpSysAlloc( + long size, /* Size of block to allocate. */ + int isBin) /* Is this a bin allocation? */ +{ + Handle hand = NULL; + ListEl * newMemoryRecord; + + if (!(memoryFlags & MEMORY_ALL_SYS)) { + + /* + * If the guard handle has been purged, throw it away and try + * to allocate it again. + */ + + if ((toolGuardHandle != NULL) && (*toolGuardHandle == NULL)) { + DisposeHandle(toolGuardHandle); + toolGuardHandle = NULL; + } + + /* + * If we have never allocated the guard handle, or it was purged + * and thrown away, then try to allocate it again. + */ + + if (toolGuardHandle == NULL) { + toolGuardHandle = NewHandle(TOOLBOX_SPACE); + if (toolGuardHandle != NULL) { + HPurge(toolGuardHandle); + } + } + + /* + * If we got the handle, lock it and do our allocation. + */ + + if (toolGuardHandle != NULL) { + HLock(toolGuardHandle); + hand = NewHandle(size + sizeof(Handle)); + HUnlock(toolGuardHandle); + } + } + if (hand != NULL) { + newMemoryRecord = (ListEl *) NewPtr(sizeof(ListEl)); + if (newMemoryRecord == NULL) { + DisposeHandle(hand); + return NULL; + } + newMemoryRecord->memoryHandle = hand; + newMemoryRecord->next = appMemory; + appMemory = newMemoryRecord; + } else { + /* + * Ran out of memory in application space. Lets try to get + * more memory from system. Otherwise, we return NULL to + * denote failure. + */ + isBin = 0; + hand = NewHandleSys(size + sizeof(Handle)); + if (hand == NULL) { + return NULL; + } + if (systemMemory == NULL) { + /* + * This is the first time we've attempted to allocate memory + * directly from the system heap. We need to now install the + * exit handle to ensure the memory is cleaned up. + */ + TclMacInstallExitToShellPatch(CleanUpExitProc); + } + newMemoryRecord = (ListEl *) NewPtrSys(sizeof(ListEl)); + if (newMemoryRecord == NULL) { + DisposeHandle(hand); + return NULL; + } + newMemoryRecord->memoryHandle = hand; + newMemoryRecord->next = systemMemory; + systemMemory = newMemoryRecord; + } + if (isBin) { + HLockHi(hand); + } else { + HLock(hand); + } + (** (Handle **) hand) = hand; + + return (*hand + sizeof(Handle)); +} + +/* + *---------------------------------------------------------------------- + * + * TclpSysFree -- + * + * Free memory that we allocated back to the system. + * + * Results: + * None. + * + * Side effects: + * Memory is freed. + * + *---------------------------------------------------------------------- + */ + +void +TclpSysFree( + void * ptr) /* Free this system memory. */ +{ + Handle hand; + OSErr err; + + hand = * (Handle *) ((Ptr) ptr - sizeof(Handle)); + DisposeHandle(hand); + *hand = NULL; + err = MemError(); +} + +/* + *---------------------------------------------------------------------- + * + * CleanUpExitProc -- + * + * This procedure is invoked as an exit handler when ExitToShell + * is called. It removes any memory that was allocated directly + * from the system heap. This must be called when the application + * quits or the memory will never be freed. + * + * Results: + * None. + * + * Side effects: + * May free memory in the system heap. + * + *---------------------------------------------------------------------- + */ + +static pascal void +CleanUpExitProc() +{ + ListEl * memRecord; + + while (systemMemory != NULL) { + memRecord = systemMemory; + systemMemory = memRecord->next; + if (*(memRecord->memoryHandle) != NULL) { + DisposeHandle(memRecord->memoryHandle); + } + DisposePtr((void *) memRecord); + } +} + +/* + *---------------------------------------------------------------------- + * + * FreeAllMemory -- + * + * This procedure frees all memory blocks allocated by the memory + * sub-system. Make sure you don't have any code that references + * any malloced data! + * + * Results: + * None. + * + * Side effects: + * Frees all memory allocated by TclpAlloc. + * + *---------------------------------------------------------------------- + */ + +void +FreeAllMemory() +{ + ListEl * memRecord; + + while (systemMemory != NULL) { + memRecord = systemMemory; + systemMemory = memRecord->next; + if (*(memRecord->memoryHandle) != NULL) { + DisposeHandle(memRecord->memoryHandle); + } + DisposePtr((void *) memRecord); + } + while (appMemory != NULL) { + memRecord = appMemory; + appMemory = memRecord->next; + if (*(memRecord->memoryHandle) != NULL) { + DisposeHandle(memRecord->memoryHandle); + } + DisposePtr((void *) memRecord); + } +} + +/* + *---------------------------------------------------------------------- + * + * ConfigureMemory -- + * + * This procedure sets certain flags in this file that control + * how memory is allocated and managed. This call must be made + * before any call to TclpAlloc is made. + * + * Results: + * None. + * + * Side effects: + * Certain state will be changed. + * + *---------------------------------------------------------------------- + */ + +void +ConfigureMemory( + int flags) /* Flags that control memory alloc scheme. */ +{ + memoryFlags = flags; +} diff --git a/tcl/mac/tclMacAppInit.c b/tcl/mac/tclMacAppInit.c new file mode 100644 index 00000000000..05cdc416da6 --- /dev/null +++ b/tcl/mac/tclMacAppInit.c @@ -0,0 +1,212 @@ +/* + * tclMacAppInit.c -- + * + * Provides a version of the Tcl_AppInit procedure for the example shell. + * + * Copyright (c) 1993-1994 Lockheed Missle & Space Company, AI Center + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tcl.h" +#include "tclInt.h" +#include "tclPort.h" +#include "tclMac.h" +#include "tclMacInt.h" + +#if defined(THINK_C) +# include <console.h> +#elif defined(__MWERKS__) +# include <SIOUX.h> +short InstallConsole _ANSI_ARGS_((short fd)); +#endif + +#ifdef TCL_TEST +EXTERN int Procbodytest_Init _ANSI_ARGS_((Tcl_Interp *interp)); +EXTERN int Procbodytest_SafeInit _ANSI_ARGS_((Tcl_Interp *interp)); +EXTERN int TclObjTest_Init _ANSI_ARGS_((Tcl_Interp *interp)); +EXTERN int Tcltest_Init _ANSI_ARGS_((Tcl_Interp *interp)); +#endif /* TCL_TEST */ + +/* + * Forward declarations for procedures defined later in this file: + */ + +static int MacintoshInit _ANSI_ARGS_((void)); + +/* + *---------------------------------------------------------------------- + * + * main -- + * + * Main program for tclsh. This file can be used as a prototype + * for other applications using the Tcl library. + * + * Results: + * None. This procedure never returns (it exits the process when + * it's done. + * + * Side effects: + * This procedure initializes the Macintosh world and then + * calls Tcl_Main. Tcl_Main will never return except to exit. + * + *---------------------------------------------------------------------- + */ + +void +main( + int argc, /* Number of arguments. */ + char **argv) /* Array of argument strings. */ +{ + char *newArgv[2]; + + if (MacintoshInit() != TCL_OK) { + Tcl_Exit(1); + } + + argc = 1; + newArgv[0] = "tclsh"; + newArgv[1] = NULL; + Tcl_Main(argc, newArgv, Tcl_AppInit); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_AppInit -- + * + * This procedure performs application-specific initialization. + * Most applications, especially those that incorporate additional + * packages, will have their own version of this procedure. + * + * Results: + * Returns a standard Tcl completion code, and leaves an error + * message in interp->result if an error occurs. + * + * Side effects: + * Depends on the startup script. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_AppInit( + Tcl_Interp *interp) /* Interpreter for application. */ +{ + if (Tcl_Init(interp) == TCL_ERROR) { + return TCL_ERROR; + } + +#ifdef TCL_TEST + if (Tcltest_Init(interp) == TCL_ERROR) { + return TCL_ERROR; + } + Tcl_StaticPackage(interp, "Tcltest", Tcltest_Init, + (Tcl_PackageInitProc *) NULL); + if (TclObjTest_Init(interp) == TCL_ERROR) { + return TCL_ERROR; + } + if (Procbodytest_Init(interp) == TCL_ERROR) { + return TCL_ERROR; + } + Tcl_StaticPackage(interp, "procbodytest", Procbodytest_Init, + Procbodytest_SafeInit); +#endif /* TCL_TEST */ + + /* + * Call the init procedures for included packages. Each call should + * look like this: + * + * if (Mod_Init(interp) == TCL_ERROR) { + * return TCL_ERROR; + * } + * + * where "Mod" is the name of the module. + */ + + /* + * Call Tcl_CreateCommand for application-specific commands, if + * they weren't already created by the init procedures called above. + * Each call would loo like this: + * + * Tcl_CreateCommand(interp, "tclName", CFuncCmd, NULL, NULL); + */ + + /* + * Specify a user-specific startup script to invoke if the application + * is run interactively. On the Mac we can specifiy either a TEXT resource + * which contains the script or the more UNIX like file location + * may also used. (I highly recommend using the resource method.) + */ + + Tcl_SetVar(interp, "tcl_rcRsrcName", "tclshrc", TCL_GLOBAL_ONLY); + /* Tcl_SetVar(interp, "tcl_rcFileName", "~/.tclshrc", TCL_GLOBAL_ONLY); */ + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * MacintoshInit -- + * + * This procedure calls initalization routines to set up a simple + * console on a Macintosh. This is necessary as the Mac doesn't + * have a stdout & stderr by default. + * + * Results: + * Returns TCL_OK if everything went fine. If it didn't the + * application should probably fail. + * + * Side effects: + * Inits the appropiate console package. + * + *---------------------------------------------------------------------- + */ + +static int +MacintoshInit() +{ +#if GENERATING68K && !GENERATINGCFM + SetApplLimit(GetApplLimit() - (TCL_MAC_68K_STACK_GROWTH)); +#endif + MaxApplZone(); + +#if defined(THINK_C) + + /* Set options for Think C console package */ + /* The console package calls the Mac init calls */ + console_options.pause_atexit = 0; + console_options.title = "\pTcl Interpreter"; + +#elif defined(__MWERKS__) + + /* Set options for CodeWarrior SIOUX package */ + SIOUXSettings.autocloseonquit = true; + SIOUXSettings.showstatusline = true; + SIOUXSettings.asktosaveonclose = false; + InstallConsole(0); + SIOUXSetTitle("\pTcl Interpreter"); + +#elif defined(applec) + + /* Init packages used by MPW SIOW package */ + InitGraf((Ptr)&qd.thePort); + InitFonts(); + InitWindows(); + InitMenus(); + TEInit(); + InitDialogs(nil); + InitCursor(); + +#endif + + Tcl_MacSetEventProc((Tcl_MacConvertEventPtr) SIOUXHandleOneEvent); + + /* No problems with initialization */ + return TCL_OK; +} diff --git a/tcl/mac/tclMacApplication.r b/tcl/mac/tclMacApplication.r new file mode 100644 index 00000000000..10e2aa39053 --- /dev/null +++ b/tcl/mac/tclMacApplication.r @@ -0,0 +1,75 @@ +/* + * tclMacApplication.r -- + * + * This file creates resources for use Tcl Shell application. + * It should be viewed as an example of how to create a new + * Tcl application using the shared Tcl libraries. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <Types.r> +#include <SysTypes.r> + +/* + * The folowing include and defines help construct + * the version string for Tcl. + */ + +#define RESOURCE_INCLUDED +#include "tcl.h" + +#if (TCL_RELEASE_LEVEL == 0) +# define RELEASE_LEVEL alpha +#elif (TCL_RELEASE_LEVEL == 1) +# define RELEASE_LEVEL beta +#elif (TCL_RELEASE_LEVEL == 2) +# define RELEASE_LEVEL final +#endif + +#if (TCL_RELEASE_LEVEL == 2) +# define MINOR_VERSION (TCL_MINOR_VERSION * 16) + TCL_RELEASE_SERIAL +#else +# define MINOR_VERSION TCL_MINOR_VERSION * 16 +#endif + +resource 'vers' (1) { + TCL_MAJOR_VERSION, MINOR_VERSION, + RELEASE_LEVEL, 0x00, verUS, + TCL_PATCH_LEVEL, + TCL_PATCH_LEVEL ", by Ray Johnson © Sun Microsystems" +}; + +resource 'vers' (2) { + TCL_MAJOR_VERSION, MINOR_VERSION, + RELEASE_LEVEL, 0x00, verUS, + TCL_PATCH_LEVEL, + "Tcl Shell " TCL_PATCH_LEVEL " © 1996" +}; + +#define TCL_APP_CREATOR 'Tcl ' + +type TCL_APP_CREATOR as 'STR '; +resource TCL_APP_CREATOR (0, purgeable) { + "Tcl Shell " TCL_PATCH_LEVEL " © 1996" +}; + +/* + * The 'kind' resource works with a 'BNDL' in Macintosh Easy Open + * to affect the text the Finder displays in the "kind" column and + * file info dialog. This information will be applied to all files + * with the listed creator and type. + */ + +resource 'kind' (128, "Tcl kind", purgeable) { + TCL_APP_CREATOR, + 0, /* region = USA */ + { + 'APPL', "Tcl Shell", + } +}; diff --git a/tcl/mac/tclMacBOAAppInit.c b/tcl/mac/tclMacBOAAppInit.c new file mode 100644 index 00000000000..9ec795b45db --- /dev/null +++ b/tcl/mac/tclMacBOAAppInit.c @@ -0,0 +1,257 @@ +/* + * tclMacBOAAppInit.c -- + * + * Provides a version of the Tcl_AppInit procedure for a + * Macintosh Background Only Application. + * + * Copyright (c) 1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tcl.h" +#include "tclInt.h" +#include "tclPort.h" +#include "tclMac.h" +#include "tclMacInt.h" +#include <Fonts.h> +#include <Windows.h> +#include <Dialogs.h> +#include <Menus.h> +#include <Aliases.h> +#include <LowMem.h> + +#include <AppleEvents.h> +#include <SegLoad.h> +#include <ToolUtils.h> + +#if defined(THINK_C) +# include <console.h> +#elif defined(__MWERKS__) +# include <SIOUX.h> +short InstallConsole _ANSI_ARGS_((short fd)); +#endif + +void TkMacInitAppleEvents(Tcl_Interp *interp); +int HandleHighLevelEvents(EventRecord *eventPtr); + +#ifdef TCL_TEST +EXTERN int TclObjTest_Init _ANSI_ARGS_((Tcl_Interp *interp)); +EXTERN int Tcltest_Init _ANSI_ARGS_((Tcl_Interp *interp)); +#endif /* TCL_TEST */ + +/* + * Forward declarations for procedures defined later in this file: + */ + +static int MacintoshInit _ANSI_ARGS_((void)); + +/* + *---------------------------------------------------------------------- + * + * main -- + * + * Main program for tclsh. This file can be used as a prototype + * for other applications using the Tcl library. + * + * Results: + * None. This procedure never returns (it exits the process when + * it's done. + * + * Side effects: + * This procedure initializes the Macintosh world and then + * calls Tcl_Main. Tcl_Main will never return except to exit. + * + *---------------------------------------------------------------------- + */ + +void +main( + int argc, /* Number of arguments. */ + char **argv) /* Array of argument strings. */ +{ + char *newArgv[3]; + + if (MacintoshInit() != TCL_OK) { + Tcl_Exit(1); + } + + argc = 2; + newArgv[0] = "tclsh"; + newArgv[1] = "bgScript.tcl"; + newArgv[2] = NULL; + Tcl_Main(argc, newArgv, Tcl_AppInit); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_AppInit -- + * + * This procedure performs application-specific initialization. + * Most applications, especially those that incorporate additional + * packages, will have their own version of this procedure. + * + * Results: + * Returns a standard Tcl completion code, and leaves an error + * message in interp->result if an error occurs. + * + * Side effects: + * Depends on the startup script. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_AppInit( + Tcl_Interp *interp) /* Interpreter for application. */ +{ + Tcl_Channel tempChan; + + if (Tcl_Init(interp) == TCL_ERROR) { + return TCL_ERROR; + } + +#ifdef TCL_TEST + if (Tcltest_Init(interp) == TCL_ERROR) { + return TCL_ERROR; + } + Tcl_StaticPackage(interp, "Tcltest", Tcltest_Init, + (Tcl_PackageInitProc *) NULL); + if (TclObjTest_Init(interp) == TCL_ERROR) { + return TCL_ERROR; + } +#endif /* TCL_TEST */ + + /* + * Call the init procedures for included packages. Each call should + * look like this: + * + * if (Mod_Init(interp) == TCL_ERROR) { + * return TCL_ERROR; + * } + * + * where "Mod" is the name of the module. + */ + + /* + * Call Tcl_CreateCommand for application-specific commands, if + * they weren't already created by the init procedures called above. + * Each call would loo like this: + * + * Tcl_CreateCommand(interp, "tclName", CFuncCmd, NULL, NULL); + */ + + /* + * Specify a user-specific startup script to invoke if the application + * is run interactively. On the Mac we can specifiy either a TEXT resource + * which contains the script or the more UNIX like file location + * may also used. (I highly recommend using the resource method.) + */ + + Tcl_SetVar(interp, "tcl_rcRsrcName", "tclshrc", TCL_GLOBAL_ONLY); + + /* Tcl_SetVar(interp, "tcl_rcFileName", "~/.tclshrc", TCL_GLOBAL_ONLY); */ + + /* + * We have to support at least the quit Apple Event. + */ + + TkMacInitAppleEvents(interp); + + /* + * Open a file channel to put stderr, stdin, stdout... + */ + + tempChan = Tcl_OpenFileChannel(interp, ":temp.in", "a+", 0); + Tcl_SetStdChannel(tempChan,TCL_STDIN); + Tcl_RegisterChannel(interp, tempChan); + Tcl_SetChannelOption(NULL, tempChan, "-translation", "cr"); + Tcl_SetChannelOption(NULL, tempChan, "-buffering", "line"); + + tempChan = Tcl_OpenFileChannel(interp, ":temp.out", "a+", 0); + Tcl_SetStdChannel(tempChan,TCL_STDOUT); + Tcl_RegisterChannel(interp, tempChan); + Tcl_SetChannelOption(NULL, tempChan, "-translation", "cr"); + Tcl_SetChannelOption(NULL, tempChan, "-buffering", "line"); + + tempChan = Tcl_OpenFileChannel(interp, ":temp.err", "a+", 0); + Tcl_SetStdChannel(tempChan,TCL_STDERR); + Tcl_RegisterChannel(interp, tempChan); + Tcl_SetChannelOption(NULL, tempChan, "-translation", "cr"); + Tcl_SetChannelOption(NULL, tempChan, "-buffering", "none"); + + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * MacintoshInit -- + * + * This procedure calls initalization routines to set up a simple + * console on a Macintosh. This is necessary as the Mac doesn't + * have a stdout & stderr by default. + * + * Results: + * Returns TCL_OK if everything went fine. If it didn't the + * application should probably fail. + * + * Side effects: + * Inits the appropiate console package. + * + *---------------------------------------------------------------------- + */ + +static int +MacintoshInit() +{ + THz theZone = GetZone(); + SysEnvRec sys; + + + /* + * There is a bug in systems earlier that 7.5.5, where a second BOA will + * get a corrupted heap. This is the fix from TechNote 1070 + */ + + SysEnvirons(1, &sys); + + if (sys.systemVersion < 0x0755) + { + if ( LMGetHeapEnd() != theZone->bkLim) { + LMSetHeapEnd(theZone->bkLim); + } + } + +#if GENERATING68K && !GENERATINGCFM + SetApplLimit(GetApplLimit() - (TCL_MAC_68K_STACK_GROWTH)); +#endif + MaxApplZone(); + + InitGraf((Ptr)&qd.thePort); + + /* No problems with initialization */ + Tcl_MacSetEventProc(HandleHighLevelEvents); + + return TCL_OK; +} + +int +HandleHighLevelEvents( + EventRecord *eventPtr) +{ + int eventFound = false; + + if (eventPtr->what == kHighLevelEvent) { + AEProcessAppleEvent(eventPtr); + eventFound = true; + } else if (eventPtr->what == nullEvent) { + eventFound = true; + } + return eventFound; +} diff --git a/tcl/mac/tclMacBOAMain.c b/tcl/mac/tclMacBOAMain.c new file mode 100644 index 00000000000..c08a39dba9e --- /dev/null +++ b/tcl/mac/tclMacBOAMain.c @@ -0,0 +1,360 @@ +/* + * tclMacBGMain.c -- + * + * Main program for Macintosh Background Only Application shells. + * + * Copyright (c) 1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tcl.h" +#include "tclInt.h" +#include "tclMacInt.h" +#include <Resources.h> +#include <Notification.h> +#include <Strings.h> + +/* + * This variable is used to get out of the modal loop of the + * notification manager. + */ + +int NotificationIsDone = 0; + +/* + * The following code ensures that tclLink.c is linked whenever + * Tcl is linked. Without this code there's no reference to the + * code in that file from anywhere in Tcl, so it may not be + * linked into the application. + */ + +EXTERN int Tcl_LinkVar(); +int (*tclDummyLinkVarPtr)() = Tcl_LinkVar; + +/* + * Declarations for various library procedures and variables (don't want + * to include tclPort.h here, because people might copy this file out of + * the Tcl source directory to make their own modified versions). + * Note: "exit" should really be declared here, but there's no way to + * declare it without causing conflicts with other definitions elsewher + * on some systems, so it's better just to leave it out. + */ + +extern int isatty _ANSI_ARGS_((int fd)); +extern char * strcpy _ANSI_ARGS_((char *dst, CONST char *src)); + +static Tcl_Interp *interp; /* Interpreter for application. */ + +#ifdef TCL_MEM_DEBUG +static char dumpFile[100]; /* Records where to dump memory allocation + * information. */ +static int quitFlag = 0; /* 1 means "checkmem" command was called, + * so the application should quit and dump + * memory allocation information. */ +#endif + +/* + * Forward references for procedures defined later in this file: + */ + +#ifdef TCL_MEM_DEBUG +static int CheckmemCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int argc, char *argv[])); +#endif +void TclMacDoNotification(char *mssg); +void TclMacNotificationResponse(NMRecPtr nmRec); +int Tcl_MacBGNotifyObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv); + + +/* + *---------------------------------------------------------------------- + * + * Tcl_Main -- + * + * Main program for tclsh and most other Tcl-based applications. + * + * Results: + * None. This procedure never returns (it exits the process when + * it's done. + * + * Side effects: + * This procedure initializes the Tk world and then starts + * interpreting commands; almost anything could happen, depending + * on the script being interpreted. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_Main(argc, argv, appInitProc) + int argc; /* Number of arguments. */ + char **argv; /* Array of argument strings. */ + Tcl_AppInitProc *appInitProc; + /* Application-specific initialization + * procedure to call after most + * initialization but before starting to + * execute commands. */ +{ + Tcl_Obj *prompt1NamePtr = NULL; + Tcl_Obj *prompt2NamePtr = NULL; + Tcl_Obj *commandPtr = NULL; + char buffer[1000], *args, *fileName; + int code, tty; + int exitCode = 0; + + Tcl_FindExecutable(argv[0]); + interp = Tcl_CreateInterp(); +#ifdef TCL_MEM_DEBUG + Tcl_InitMemory(interp); + Tcl_CreateCommand(interp, "checkmem", CheckmemCmd, (ClientData) 0, + (Tcl_CmdDeleteProc *) NULL); +#endif + + /* + * Make command-line arguments available in the Tcl variables "argc" + * and "argv". If the first argument doesn't start with a "-" then + * strip it off and use it as the name of a script file to process. + */ + + fileName = NULL; + if ((argc > 1) && (argv[1][0] != '-')) { + fileName = argv[1]; + argc--; + argv++; + } + args = Tcl_Merge(argc-1, argv+1); + Tcl_SetVar(interp, "argv", args, TCL_GLOBAL_ONLY); + ckfree(args); + TclFormatInt(buffer, argc-1); + Tcl_SetVar(interp, "argc", buffer, TCL_GLOBAL_ONLY); + Tcl_SetVar(interp, "argv0", (fileName != NULL) ? fileName : argv[0], + TCL_GLOBAL_ONLY); + + /* + * Set the "tcl_interactive" variable. + */ + + tty = isatty(0); + Tcl_SetVar(interp, "tcl_interactive", + ((fileName == NULL) && tty) ? "1" : "0", TCL_GLOBAL_ONLY); + + /* + * Invoke application-specific initialization. + */ + + if ((*appInitProc)(interp) != TCL_OK) { + Tcl_DString errStr; + Tcl_DStringInit(&errStr); + Tcl_DStringAppend(&errStr, + "application-specific initialization failed: \n", -1); + Tcl_DStringAppend(&errStr, interp->result, -1); + Tcl_DStringAppend(&errStr, "\n", 1); + TclMacDoNotification(Tcl_DStringValue(&errStr)); + goto done; + } + + /* + * Install the BGNotify command: + */ + + if ( Tcl_CreateObjCommand(interp, "bgnotify", Tcl_MacBGNotifyObjCmd, NULL, + (Tcl_CmdDeleteProc *) NULL) == NULL) { + goto done; + } + + /* + * If a script file was specified then just source that file + * and quit. In this Mac BG Application version, we will try the + * resource fork first, then the file system second... + */ + + if (fileName != NULL) { + Str255 resName; + Handle resource; + + strcpy((char *) resName + 1, fileName); + resName[0] = strlen(fileName); + resource = GetNamedResource('TEXT',resName); + if (resource != NULL) { + code = Tcl_MacEvalResource(interp, fileName, -1, NULL); + } else { + code = Tcl_EvalFile(interp, fileName); + } + + if (code != TCL_OK) { + Tcl_DString errStr; + + Tcl_DStringInit(&errStr); + Tcl_DStringAppend(&errStr, " Error sourcing resource or file: ", -1); + Tcl_DStringAppend(&errStr, fileName, -1); + Tcl_DStringAppend(&errStr, "\n\nError was: ", -1); + Tcl_DStringAppend(&errStr, interp->result, -1); + + TclMacDoNotification(Tcl_DStringValue(&errStr)); + + } + goto done; + } + + + /* + * Rather than calling exit, invoke the "exit" command so that + * users can replace "exit" with some other command to do additional + * cleanup on exit. The Tcl_Eval call should never return. + */ + + done: + if (commandPtr != NULL) { + Tcl_DecrRefCount(commandPtr); + } + if (prompt1NamePtr != NULL) { + Tcl_DecrRefCount(prompt1NamePtr); + } + if (prompt2NamePtr != NULL) { + Tcl_DecrRefCount(prompt2NamePtr); + } + sprintf(buffer, "exit %d", exitCode); + Tcl_Eval(interp, buffer); +} + +/*---------------------------------------------------------------------- + * + * TclMacDoNotification -- + * + * This posts an error message using the Notification manager. + * + * Results: + * Post a Notification Manager dialog. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +void +TclMacDoNotification(mssg) + char *mssg; +{ + NMRec errorNot; + EventRecord *theEvent = NULL; + OSErr err; + char *ptr; + + errorNot.qType = nmType; + errorNot.nmMark = 0; + errorNot.nmIcon = 0; + errorNot.nmSound = (Handle) -1; + + for ( ptr = mssg; *ptr != '\0'; ptr++) { + if (*ptr == '\n') { + *ptr = '\r'; + } + } + + c2pstr(mssg); + errorNot.nmStr = (StringPtr) mssg; + + errorNot.nmResp = NewNMProc(TclMacNotificationResponse); + errorNot.nmRefCon = SetCurrentA5(); + + NotificationIsDone = 0; + + /* + * Cycle while waiting for the user to click on the + * notification box. Don't take any events off the event queue, + * since we want Tcl to do this but we want to block till the notification + * has been handled... + */ + + err = NMInstall(&errorNot); + if (err == noErr) { + while (!NotificationIsDone) { + WaitNextEvent(0, theEvent, 20, NULL); + } + NMRemove(&errorNot); + } + + p2cstr((unsigned char *) mssg); +} + +void +TclMacNotificationResponse(nmRec) + NMRecPtr nmRec; +{ + int curA5; + + curA5 = SetCurrentA5(); + SetA5(nmRec->nmRefCon); + + NotificationIsDone = 1; + + SetA5(curA5); + +} + +int +Tcl_MacBGNotifyObjCmd(clientData, interp, objc, objv) + ClientData clientData; + Tcl_Interp *interp; + int objc; + Tcl_Obj **objv; +{ + Tcl_Obj *resultPtr; + + resultPtr = Tcl_GetObjResult(interp); + + if ( objc != 2 ) { + Tcl_WrongNumArgs(interp, 1, objv, "message"); + return TCL_ERROR; + } + + TclMacDoNotification(Tcl_GetStringFromObj(objv[1], (int *) NULL)); + return TCL_OK; + +} + + +/* + *---------------------------------------------------------------------- + * + * CheckmemCmd -- + * + * This is the command procedure for the "checkmem" command, which + * causes the application to exit after printing information about + * memory usage to the file passed to this command as its first + * argument. + * + * Results: + * Returns a standard Tcl completion code. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +#ifdef TCL_MEM_DEBUG + + /* ARGSUSED */ +static int +CheckmemCmd(clientData, interp, argc, argv) + ClientData clientData; /* Not used. */ + Tcl_Interp *interp; /* Interpreter for evaluation. */ + int argc; /* Number of arguments. */ + char *argv[]; /* String values of arguments. */ +{ + extern char *tclMemDumpFileName; + if (argc != 2) { + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " fileName\"", (char *) NULL); + return TCL_ERROR; + } + strcpy(dumpFile, argv[1]); + tclMemDumpFileName = dumpFile; + quitFlag = 1; + return TCL_OK; +} +#endif diff --git a/tcl/mac/tclMacChan.c b/tcl/mac/tclMacChan.c new file mode 100644 index 00000000000..ffb91c73d94 --- /dev/null +++ b/tcl/mac/tclMacChan.c @@ -0,0 +1,1356 @@ +/* + * tclMacChan.c + * + * Channel drivers for Macintosh channels for the + * console fds. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tclInt.h" +#include "tclPort.h" +#include "tclMacInt.h" +#include <Aliases.h> +#include <Errors.h> +#include <Files.h> +#include <Gestalt.h> +#include <Processes.h> +#include <Strings.h> +#include <FSpCompat.h> +#include <MoreFiles.h> +#include <MoreFilesExtras.h> + +/* + * The following variable is used to tell whether this module has been + * initialized. + */ + +static int initialized = 0; + +/* + * The following are flags returned by GetOpenMode. They + * are or'd together to determine how opening and handling + * a file should occur. + */ + +#define TCL_RDONLY (1<<0) +#define TCL_WRONLY (1<<1) +#define TCL_RDWR (1<<2) +#define TCL_CREAT (1<<3) +#define TCL_TRUNC (1<<4) +#define TCL_APPEND (1<<5) +#define TCL_ALWAYS_APPEND (1<<6) +#define TCL_EXCL (1<<7) +#define TCL_NOCTTY (1<<8) +#define TCL_NONBLOCK (1<<9) +#define TCL_RW_MODES (TCL_RDONLY|TCL_WRONLY|TCL_RDWR) + +/* + * This structure describes per-instance state of a + * macintosh file based channel. + */ + +typedef struct FileState { + short fileRef; /* Macintosh file reference number. */ + Tcl_Channel fileChan; /* Pointer to the channel for this file. */ + int watchMask; /* OR'ed set of flags indicating which events + * are being watched. */ + int appendMode; /* Flag to tell if in O_APPEND mode or not. */ + int volumeRef; /* Flag to tell if in O_APPEND mode or not. */ + int pending; /* 1 if message is pending on queue. */ + struct FileState *nextPtr; /* Pointer to next registered file. */ +} FileState; + +/* + * The following pointer refers to the head of the list of files managed + * that are being watched for file events. + */ + +static FileState *firstFilePtr; + +/* + * The following structure is what is added to the Tcl event queue when + * file events are generated. + */ + +typedef struct FileEvent { + Tcl_Event header; /* Information that is standard for + * all events. */ + FileState *infoPtr; /* Pointer to file info structure. Note + * that we still have to verify that the + * file exists before dereferencing this + * pointer. */ +} FileEvent; + + +/* + * Static routines for this file: + */ + +static int CommonGetHandle _ANSI_ARGS_((ClientData instanceData, + int direction, ClientData *handlePtr)); +static void CommonWatch _ANSI_ARGS_((ClientData instanceData, + int mask)); +static int FileBlockMode _ANSI_ARGS_((ClientData instanceData, + int mode)); +static void FileChannelExitHandler _ANSI_ARGS_(( + ClientData clientData)); +static void FileCheckProc _ANSI_ARGS_((ClientData clientData, + int flags)); +static int FileClose _ANSI_ARGS_((ClientData instanceData, + Tcl_Interp *interp)); +static int FileEventProc _ANSI_ARGS_((Tcl_Event *evPtr, + int flags)); +static void FileInit _ANSI_ARGS_((void)); +static int FileInput _ANSI_ARGS_((ClientData instanceData, + char *buf, int toRead, int *errorCode)); +static int FileOutput _ANSI_ARGS_((ClientData instanceData, + char *buf, int toWrite, int *errorCode)); +static int FileSeek _ANSI_ARGS_((ClientData instanceData, + long offset, int mode, int *errorCode)); +static void FileSetupProc _ANSI_ARGS_((ClientData clientData, + int flags)); +static int GetOpenMode _ANSI_ARGS_((Tcl_Interp *interp, + char *string)); +static Tcl_Channel OpenFileChannel _ANSI_ARGS_((char *fileName, int mode, + int permissions, int *errorCodePtr)); +static int StdIOBlockMode _ANSI_ARGS_((ClientData instanceData, + int mode)); +static int StdIOClose _ANSI_ARGS_((ClientData instanceData, + Tcl_Interp *interp)); +static int StdIOInput _ANSI_ARGS_((ClientData instanceData, + char *buf, int toRead, int *errorCode)); +static int StdIOOutput _ANSI_ARGS_((ClientData instanceData, + char *buf, int toWrite, int *errorCode)); +static int StdIOSeek _ANSI_ARGS_((ClientData instanceData, + long offset, int mode, int *errorCode)); +static int StdReady _ANSI_ARGS_((ClientData instanceData, + int mask)); + +/* + * This structure describes the channel type structure for file based IO: + */ + +static Tcl_ChannelType consoleChannelType = { + "file", /* Type name. */ + StdIOBlockMode, /* Set blocking/nonblocking mode.*/ + StdIOClose, /* Close proc. */ + StdIOInput, /* Input proc. */ + StdIOOutput, /* Output proc. */ + StdIOSeek, /* Seek proc. */ + NULL, /* Set option proc. */ + NULL, /* Get option proc. */ + CommonWatch, /* Initialize notifier. */ + CommonGetHandle /* Get OS handles out of channel. */ +}; + +/* + * This variable describes the channel type structure for file based IO. + */ + +static Tcl_ChannelType fileChannelType = { + "file", /* Type name. */ + FileBlockMode, /* Set blocking or + * non-blocking mode.*/ + FileClose, /* Close proc. */ + FileInput, /* Input proc. */ + FileOutput, /* Output proc. */ + FileSeek, /* Seek proc. */ + NULL, /* Set option proc. */ + NULL, /* Get option proc. */ + CommonWatch, /* Initialize notifier. */ + CommonGetHandle /* Get OS handles out of channel. */ +}; + + +/* + * Hack to allow Mac Tk to override the TclGetStdChannels function. + */ + +typedef void (*TclGetStdChannelsProc) _ANSI_ARGS_((Tcl_Channel *stdinPtr, + Tcl_Channel *stdoutPtr, Tcl_Channel *stderrPtr)); + +TclGetStdChannelsProc getStdChannelsProc = NULL; + +/* + * Static variables to hold channels for stdin, stdout and stderr. + */ + +static Tcl_Channel stdinChannel = NULL; +static Tcl_Channel stdoutChannel = NULL; +static Tcl_Channel stderrChannel = NULL; + +/* + *---------------------------------------------------------------------- + * + * FileInit -- + * + * This function initializes the file channel event source. + * + * Results: + * None. + * + * Side effects: + * Creates a new event source. + * + *---------------------------------------------------------------------- + */ + +static void +FileInit() +{ + initialized = 1; + firstFilePtr = NULL; + Tcl_CreateEventSource(FileSetupProc, FileCheckProc, NULL); + Tcl_CreateExitHandler(FileChannelExitHandler, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * FileChannelExitHandler -- + * + * This function is called to cleanup the channel driver before + * Tcl is unloaded. + * + * Results: + * None. + * + * Side effects: + * Destroys the communication window. + * + *---------------------------------------------------------------------- + */ + +static void +FileChannelExitHandler( + ClientData clientData) /* Old window proc */ +{ + Tcl_DeleteEventSource(FileSetupProc, FileCheckProc, NULL); + initialized = 0; +} + +/* + *---------------------------------------------------------------------- + * + * FileSetupProc -- + * + * This procedure is invoked before Tcl_DoOneEvent blocks waiting + * for an event. + * + * Results: + * None. + * + * Side effects: + * Adjusts the block time if needed. + * + *---------------------------------------------------------------------- + */ + +void +FileSetupProc( + ClientData data, /* Not used. */ + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + FileState *infoPtr; + Tcl_Time blockTime = { 0, 0 }; + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + /* + * Check to see if there is a ready file. If so, poll. + */ + + for (infoPtr = firstFilePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { + if (infoPtr->watchMask) { + Tcl_SetMaxBlockTime(&blockTime); + break; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FileCheckProc -- + * + * This procedure is called by Tcl_DoOneEvent to check the file + * event source for events. + * + * Results: + * None. + * + * Side effects: + * May queue an event. + * + *---------------------------------------------------------------------- + */ + +static void +FileCheckProc( + ClientData data, /* Not used. */ + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + FileEvent *evPtr; + FileState *infoPtr; + int sentMsg = 0; + Tcl_Time blockTime = { 0, 0 }; + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + /* + * Queue events for any ready files that don't already have events + * queued (caused by persistent states that won't generate WinSock + * events). + */ + + for (infoPtr = firstFilePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { + if (infoPtr->watchMask && !infoPtr->pending) { + infoPtr->pending = 1; + evPtr = (FileEvent *) ckalloc(sizeof(FileEvent)); + evPtr->header.proc = FileEventProc; + evPtr->infoPtr = infoPtr; + Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); + } + } +} + +/*---------------------------------------------------------------------- + * + * FileEventProc -- + * + * This function is invoked by Tcl_ServiceEvent when a file event + * reaches the front of the event queue. This procedure invokes + * Tcl_NotifyChannel on the file. + * + * Results: + * Returns 1 if the event was handled, meaning it should be removed + * from the queue. Returns 0 if the event was not handled, meaning + * it should stay on the queue. The only time the event isn't + * handled is if the TCL_FILE_EVENTS flag bit isn't set. + * + * Side effects: + * Whatever the notifier callback does. + * + *---------------------------------------------------------------------- + */ + +static int +FileEventProc( + Tcl_Event *evPtr, /* Event to service. */ + int flags) /* Flags that indicate what events to + * handle, such as TCL_FILE_EVENTS. */ +{ + FileEvent *fileEvPtr = (FileEvent *)evPtr; + FileState *infoPtr; + + if (!(flags & TCL_FILE_EVENTS)) { + return 0; + } + + /* + * Search through the list of watched files for the one whose handle + * matches the event. We do this rather than simply dereferencing + * the handle in the event so that files can be deleted while the + * event is in the queue. + */ + + for (infoPtr = firstFilePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { + if (fileEvPtr->infoPtr == infoPtr) { + infoPtr->pending = 0; + Tcl_NotifyChannel(infoPtr->fileChan, infoPtr->watchMask); + break; + } + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * StdIOBlockMode -- + * + * Set blocking or non-blocking mode on channel. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or non-blocking mode. + * + *---------------------------------------------------------------------- + */ + +static int +StdIOBlockMode( + ClientData instanceData, /* Unused. */ + int mode) /* The mode to set. */ +{ + /* + * Do not allow putting stdin, stdout or stderr into nonblocking mode. + */ + + if (mode == TCL_MODE_NONBLOCKING) { + return EFAULT; + } + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * StdIOClose -- + * + * Closes the IO channel. + * + * Results: + * 0 if successful, the value of errno if failed. + * + * Side effects: + * Closes the physical channel + * + *---------------------------------------------------------------------- + */ + +static int +StdIOClose( + ClientData instanceData, /* Unused. */ + Tcl_Interp *interp) /* Unused. */ +{ + int fd, errorCode = 0; + + /* + * Invalidate the stdio cache if necessary. Note that we assume that + * the stdio file and channel pointers will become invalid at the same + * time. + */ + + fd = (int) ((FileState*)instanceData)->fileRef; + if (fd == 0) { + fd = 0; + stdinChannel = NULL; + } else if (fd == 1) { + stdoutChannel = NULL; + } else if (fd == 2) { + stderrChannel = NULL; + } else { + panic("recieved invalid std file"); + } + + if (close(fd) < 0) { + errorCode = errno; + } + + return errorCode; +} + +/* + *---------------------------------------------------------------------- + * + * CommonGetHandle -- + * + * Called from Tcl_GetChannelFile to retrieve OS handles from inside + * a file based channel. + * + * Results: + * The appropriate handle or NULL if not present. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +CommonGetHandle( + ClientData instanceData, /* The file state. */ + int direction, /* Which handle to retrieve? */ + ClientData *handlePtr) +{ + if ((direction == TCL_READABLE) || (direction == TCL_WRITABLE)) { + *handlePtr = (ClientData) ((FileState*)instanceData)->fileRef; + return TCL_OK; + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * StdIOInput -- + * + * Reads input from the IO channel into the buffer given. Returns + * count of how many bytes were actually read, and an error indication. + * + * Results: + * A count of how many bytes were read is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Reads input from the actual channel. + * + *---------------------------------------------------------------------- + */ + +int +StdIOInput( + ClientData instanceData, /* Unused. */ + char *buf, /* Where to store data read. */ + int bufSize, /* How much space is available + * in the buffer? */ + int *errorCode) /* Where to store error code. */ +{ + int fd; + int bytesRead; /* How many bytes were read? */ + + *errorCode = 0; + errno = 0; + fd = (int) ((FileState*)instanceData)->fileRef; + bytesRead = read(fd, buf, (size_t) bufSize); + if (bytesRead > -1) { + return bytesRead; + } + *errorCode = errno; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * StdIOOutput-- + * + * Writes the given output on the IO channel. Returns count of how + * many characters were actually written, and an error indication. + * + * Results: + * A count of how many characters were written is returned and an + * error indication is returned in an output argument. + * + * Side effects: + * Writes output on the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +StdIOOutput( + ClientData instanceData, /* Unused. */ + char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCode) /* Where to store error code. */ +{ + int written; + int fd; + + *errorCode = 0; + errno = 0; + fd = (int) ((FileState*)instanceData)->fileRef; + written = write(fd, buf, (size_t) toWrite); + if (written > -1) { + return written; + } + *errorCode = errno; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * StdIOSeek -- + * + * Seeks on an IO channel. Returns the new position. + * + * Results: + * -1 if failed, the new position if successful. If failed, it + * also sets *errorCodePtr to the error code. + * + * Side effects: + * Moves the location at which the channel will be accessed in + * future operations. + * + *---------------------------------------------------------------------- + */ + +static int +StdIOSeek( + ClientData instanceData, /* Unused. */ + long offset, /* Offset to seek to. */ + int mode, /* Relative to where + * should we seek? */ + int *errorCodePtr) /* To store error code. */ +{ + int newLoc; + int fd; + + *errorCodePtr = 0; + fd = (int) ((FileState*)instanceData)->fileRef; + newLoc = lseek(fd, offset, mode); + if (newLoc > -1) { + return newLoc; + } + *errorCodePtr = errno; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_PidObjCmd -- + * + * This procedure is invoked to process the "pid" Tcl command. + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + + /* ARGSUSED */ +int +Tcl_PidObjCmd(dummy, interp, objc, objv) + ClientData dummy; /* Not used. */ + Tcl_Interp *interp; /* Current interpreter. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST *objv; /* Argument strings. */ +{ + ProcessSerialNumber psn; + char buf[20]; + Tcl_Channel chan; + Tcl_Obj *resultPtr; + + if (objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "?channelId?"); + return TCL_ERROR; + } + if (objc == 1) { + resultPtr = Tcl_GetObjResult(interp); + GetCurrentProcess(&psn); + sprintf(buf, "0x%08x%08x", psn.highLongOfPSN, psn.lowLongOfPSN); + Tcl_SetStringObj(resultPtr, buf, -1); + } else { + chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], NULL), + NULL); + if (chan == (Tcl_Channel) NULL) { + return TCL_ERROR; + } + /* + * We can't create pipelines on the Mac so + * this will always return an empty list. + */ + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclGetDefaultStdChannel -- + * + * Constructs a channel for the specified standard OS handle. + * + * Results: + * Returns the specified default standard channel, or NULL. + * + * Side effects: + * May cause the creation of a standard channel and the underlying + * file. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +TclGetDefaultStdChannel( + int type) /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR. */ +{ + Tcl_Channel channel = NULL; + int fd = 0; /* Initializations needed to prevent */ + int mode = 0; /* compiler warning (used before set). */ + char *bufMode = NULL; + char channelName[20]; + int channelPermissions; + FileState *fileState; + + /* + * If the channels were not created yet, create them now and + * store them in the static variables. + */ + + switch (type) { + case TCL_STDIN: + fd = 0; + channelPermissions = TCL_READABLE; + bufMode = "line"; + break; + case TCL_STDOUT: + fd = 1; + channelPermissions = TCL_WRITABLE; + bufMode = "line"; + break; + case TCL_STDERR: + fd = 2; + channelPermissions = TCL_WRITABLE; + bufMode = "none"; + break; + default: + panic("TclGetDefaultStdChannel: Unexpected channel type"); + break; + } + + sprintf(channelName, "console%d", (int) fd); + fileState = (FileState *) ckalloc((unsigned) sizeof(FileState)); + channel = Tcl_CreateChannel(&consoleChannelType, channelName, + (ClientData) fileState, channelPermissions); + fileState->fileChan = channel; + fileState->fileRef = fd; + + /* + * Set up the normal channel options for stdio handles. + */ + + Tcl_SetChannelOption(NULL, channel, "-translation", "cr"); + Tcl_SetChannelOption(NULL, channel, "-buffering", bufMode); + + return channel; +} + +/* + *---------------------------------------------------------------------- + * + * TclpOpenFileChannel -- + * + * Open an File based channel on Unix systems. + * + * Results: + * The new channel or NULL. If NULL, the output argument + * errorCodePtr is set to a POSIX error. + * + * Side effects: + * May open the channel and may cause creation of a file on the + * file system. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +TclpOpenFileChannel( + Tcl_Interp *interp, /* Interpreter for error reporting; + * can be NULL. */ + char *fileName, /* Name of file to open. */ + char *modeString, /* A list of POSIX open modes or + * a string such as "rw". */ + int permissions) /* If the open involves creating a + * file, with what modes to create + * it? */ +{ + Tcl_Channel chan; + int mode; + char *nativeName; + Tcl_DString buffer; + int errorCode; + + mode = GetOpenMode(interp, modeString); + if (mode == -1) { + return NULL; + } + + nativeName = Tcl_TranslateFileName(interp, fileName, &buffer); + if (nativeName == NULL) { + return NULL; + } + + chan = OpenFileChannel(nativeName, mode, permissions, &errorCode); + Tcl_DStringFree(&buffer); + + if (chan == NULL) { + Tcl_SetErrno(errorCode); + if (interp != (Tcl_Interp *) NULL) { + Tcl_AppendResult(interp, "couldn't open \"", fileName, "\": ", + Tcl_PosixError(interp), (char *) NULL); + } + return NULL; + } + + return chan; +} + +/* + *---------------------------------------------------------------------- + * + * OpenFileChannel-- + * + * Opens a Macintosh file and creates a Tcl channel to control it. + * + * Results: + * A Tcl channel. + * + * Side effects: + * Will open a Macintosh file. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Channel +OpenFileChannel( + char *fileName, /* Name of file to open. */ + int mode, /* Mode for opening file. */ + int permissions, /* If the open involves creating a + * file, with what modes to create + * it? */ + int *errorCodePtr) /* Where to store error code. */ +{ + int channelPermissions; + Tcl_Channel chan; + char macPermision; + FSSpec fileSpec; + OSErr err; + short fileRef; + FileState *fileState; + char channelName[64]; + + /* + * Note we use fsRdWrShPerm instead of fsRdWrPerm which allows shared + * writes on a file. This isn't common on a mac but is common with + * Windows and UNIX and the feature is used by Tcl. + */ + + switch (mode & (TCL_RDONLY | TCL_WRONLY | TCL_RDWR)) { + case TCL_RDWR: + channelPermissions = (TCL_READABLE | TCL_WRITABLE); + macPermision = fsRdWrShPerm; + break; + case TCL_WRONLY: + /* + * Mac's fsRdPerm permission actually defaults to fsRdWrPerm because + * the Mac OS doesn't realy support write only access. We explicitly + * set the permission fsRdWrShPerm so that we can have shared write + * access. + */ + channelPermissions = TCL_WRITABLE; + macPermision = fsRdWrShPerm; + break; + case TCL_RDONLY: + default: + channelPermissions = TCL_READABLE; + macPermision = fsRdPerm; + break; + } + + err = FSpLocationFromPath(strlen(fileName), fileName, &fileSpec); + if ((err != noErr) && (err != fnfErr)) { + *errorCodePtr = errno = TclMacOSErrorToPosixError(err); + Tcl_SetErrno(errno); + return NULL; + } + + if ((err == fnfErr) && (mode & TCL_CREAT)) { + err = HCreate(fileSpec.vRefNum, fileSpec.parID, fileSpec.name, 'MPW ', 'TEXT'); + if (err != noErr) { + *errorCodePtr = errno = TclMacOSErrorToPosixError(err); + Tcl_SetErrno(errno); + return NULL; + } + } else if ((mode & TCL_CREAT) && (mode & TCL_EXCL)) { + *errorCodePtr = errno = EEXIST; + Tcl_SetErrno(errno); + return NULL; + } + + err = HOpenDF(fileSpec.vRefNum, fileSpec.parID, fileSpec.name, macPermision, &fileRef); + if (err != noErr) { + *errorCodePtr = errno = TclMacOSErrorToPosixError(err); + Tcl_SetErrno(errno); + return NULL; + } + + if (mode & TCL_TRUNC) { + SetEOF(fileRef, 0); + } + + sprintf(channelName, "file%d", (int) fileRef); + fileState = (FileState *) ckalloc((unsigned) sizeof(FileState)); + chan = Tcl_CreateChannel(&fileChannelType, channelName, + (ClientData) fileState, channelPermissions); + if (chan == (Tcl_Channel) NULL) { + *errorCodePtr = errno = EFAULT; + Tcl_SetErrno(errno); + FSClose(fileRef); + ckfree((char *) fileState); + return NULL; + } + + fileState->fileChan = chan; + fileState->volumeRef = fileSpec.vRefNum; + fileState->fileRef = fileRef; + fileState->pending = 0; + fileState->watchMask = 0; + if (mode & TCL_ALWAYS_APPEND) { + fileState->appendMode = true; + } else { + fileState->appendMode = false; + } + + if ((mode & TCL_ALWAYS_APPEND) || (mode & TCL_APPEND)) { + if (Tcl_Seek(chan, 0, SEEK_END) < 0) { + *errorCodePtr = errno = EFAULT; + Tcl_SetErrno(errno); + Tcl_Close(NULL, chan); + FSClose(fileRef); + ckfree((char *) fileState); + return NULL; + } + } + + return chan; +} + +/* + *---------------------------------------------------------------------- + * + * FileBlockMode -- + * + * Set blocking or non-blocking mode on channel. Macintosh files + * can never really be set to blocking or non-blocking modes. + * However, we don't generate an error - we just return success. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or non-blocking mode. + * + *---------------------------------------------------------------------- + */ + +static int +FileBlockMode( + ClientData instanceData, /* Unused. */ + int mode) /* The mode to set. */ +{ + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FileClose -- + * + * Closes the IO channel. + * + * Results: + * 0 if successful, the value of errno if failed. + * + * Side effects: + * Closes the physical channel + * + *---------------------------------------------------------------------- + */ + +static int +FileClose( + ClientData instanceData, /* Unused. */ + Tcl_Interp *interp) /* Unused. */ +{ + FileState *fileState = (FileState *) instanceData; + int errorCode = 0; + OSErr err; + + err = FSClose(fileState->fileRef); + FlushVol(NULL, fileState->volumeRef); + if (err != noErr) { + errorCode = errno = TclMacOSErrorToPosixError(err); + panic("error during file close"); + } + + ckfree((char *) fileState); + Tcl_SetErrno(errorCode); + return errorCode; +} + +/* + *---------------------------------------------------------------------- + * + * FileInput -- + * + * Reads input from the IO channel into the buffer given. Returns + * count of how many bytes were actually read, and an error indication. + * + * Results: + * A count of how many bytes were read is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Reads input from the actual channel. + * + *---------------------------------------------------------------------- + */ + +int +FileInput( + ClientData instanceData, /* Unused. */ + char *buffer, /* Where to store data read. */ + int bufSize, /* How much space is available + * in the buffer? */ + int *errorCodePtr) /* Where to store error code. */ +{ + FileState *fileState = (FileState *) instanceData; + OSErr err; + long length = bufSize; + + *errorCodePtr = 0; + errno = 0; + err = FSRead(fileState->fileRef, &length, buffer); + if ((err == noErr) || (err == eofErr)) { + return length; + } else { + switch (err) { + case ioErr: + *errorCodePtr = errno = EIO; + case afpAccessDenied: + *errorCodePtr = errno = EACCES; + default: + *errorCodePtr = errno = EINVAL; + } + return -1; + } + *errorCodePtr = errno; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * FileOutput-- + * + * Writes the given output on the IO channel. Returns count of how + * many characters were actually written, and an error indication. + * + * Results: + * A count of how many characters were written is returned and an + * error indication is returned in an output argument. + * + * Side effects: + * Writes output on the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +FileOutput( + ClientData instanceData, /* Unused. */ + char *buffer, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCodePtr) /* Where to store error code. */ +{ + FileState *fileState = (FileState *) instanceData; + long length = toWrite; + OSErr err; + + *errorCodePtr = 0; + errno = 0; + + if (fileState->appendMode == true) { + FileSeek(instanceData, 0, SEEK_END, errorCodePtr); + *errorCodePtr = 0; + } + + err = FSWrite(fileState->fileRef, &length, buffer); + if (err == noErr) { + err = FlushFile(fileState->fileRef); + } else { + *errorCodePtr = errno = TclMacOSErrorToPosixError(err); + return -1; + } + return length; +} + +/* + *---------------------------------------------------------------------- + * + * FileSeek -- + * + * Seeks on an IO channel. Returns the new position. + * + * Results: + * -1 if failed, the new position if successful. If failed, it + * also sets *errorCodePtr to the error code. + * + * Side effects: + * Moves the location at which the channel will be accessed in + * future operations. + * + *---------------------------------------------------------------------- + */ + +static int +FileSeek( + ClientData instanceData, /* Unused. */ + long offset, /* Offset to seek to. */ + int mode, /* Relative to where + * should we seek? */ + int *errorCodePtr) /* To store error code. */ +{ + FileState *fileState = (FileState *) instanceData; + IOParam pb; + OSErr err; + + *errorCodePtr = 0; + pb.ioCompletion = NULL; + pb.ioRefNum = fileState->fileRef; + if (mode == SEEK_SET) { + pb.ioPosMode = fsFromStart; + } else if (mode == SEEK_END) { + pb.ioPosMode = fsFromLEOF; + } else if (mode == SEEK_CUR) { + err = PBGetFPosSync((ParmBlkPtr) &pb); + if (pb.ioResult == noErr) { + if (offset == 0) { + return pb.ioPosOffset; + } + offset += pb.ioPosOffset; + } + pb.ioPosMode = fsFromStart; + } + pb.ioPosOffset = offset; + err = PBSetFPosSync((ParmBlkPtr) &pb); + if (pb.ioResult == noErr){ + return pb.ioPosOffset; + } else if (pb.ioResult == eofErr) { + long currentEOF, newEOF; + long buffer, i, length; + + err = PBGetEOFSync((ParmBlkPtr) &pb); + currentEOF = (long) pb.ioMisc; + if (mode == SEEK_SET) { + newEOF = offset; + } else if (mode == SEEK_END) { + newEOF = offset + currentEOF; + } else if (mode == SEEK_CUR) { + err = PBGetFPosSync((ParmBlkPtr) &pb); + newEOF = offset + pb.ioPosOffset; + } + + /* + * Write 0's to the new EOF. + */ + pb.ioPosOffset = 0; + pb.ioPosMode = fsFromLEOF; + err = PBGetFPosSync((ParmBlkPtr) &pb); + length = 1; + buffer = 0; + for (i = 0; i < (newEOF - currentEOF); i++) { + err = FSWrite(fileState->fileRef, &length, &buffer); + } + err = PBGetFPosSync((ParmBlkPtr) &pb); + if (pb.ioResult == noErr){ + return pb.ioPosOffset; + } + } + *errorCodePtr = errno = TclMacOSErrorToPosixError(err); + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * CommonWatch -- + * + * Initialize the notifier to watch handles from this channel. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +CommonWatch( + ClientData instanceData, /* The file state. */ + int mask) /* Events of interest; an OR-ed + * combination of TCL_READABLE, + * TCL_WRITABLE and TCL_EXCEPTION. */ +{ + FileState **nextPtrPtr, *ptr; + FileState *infoPtr = (FileState *) instanceData; + int oldMask = infoPtr->watchMask; + + if (!initialized) { + FileInit(); + } + + infoPtr->watchMask = mask; + if (infoPtr->watchMask) { + if (!oldMask) { + infoPtr->nextPtr = firstFilePtr; + firstFilePtr = infoPtr; + } + } else { + if (oldMask) { + /* + * Remove the file from the list of watched files. + */ + + for (nextPtrPtr = &firstFilePtr, ptr = *nextPtrPtr; + ptr != NULL; + nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { + if (infoPtr == ptr) { + *nextPtrPtr = ptr->nextPtr; + break; + } + } + } + } +} + +/* + *---------------------------------------------------------------------- + * + * GetOpenMode -- + * + * Description: + * Computes a POSIX mode mask from a given string and also sets + * a flag to indicate whether the caller should seek to EOF during + * opening of the file. + * + * Results: + * On success, returns mode to pass to "open". If an error occurs, the + * returns -1 and if interp is not NULL, sets interp->result to an + * error message. + * + * Side effects: + * Sets the integer referenced by seekFlagPtr to 1 if the caller + * should seek to EOF during opening the file. + * + * Special note: + * This code is based on a prototype implementation contributed + * by Mark Diekhans. + * + *---------------------------------------------------------------------- + */ + +static int +GetOpenMode( + Tcl_Interp *interp, /* Interpreter to use for error + * reporting - may be NULL. */ + char *string) /* Mode string, e.g. "r+" or + * "RDONLY CREAT". */ +{ + int mode, modeArgc, c, i, gotRW; + char **modeArgv, *flag; + + /* + * Check for the simpler fopen-like access modes (e.g. "r"). They + * are distinguished from the POSIX access modes by the presence + * of a lower-case first letter. + */ + + mode = 0; + if (islower(UCHAR(string[0]))) { + switch (string[0]) { + case 'r': + mode = TCL_RDONLY; + break; + case 'w': + mode = TCL_WRONLY|TCL_CREAT|TCL_TRUNC; + break; + case 'a': + mode = TCL_WRONLY|TCL_CREAT|TCL_APPEND; + break; + default: + error: + if (interp != (Tcl_Interp *) NULL) { + Tcl_AppendResult(interp, + "illegal access mode \"", string, "\"", + (char *) NULL); + } + return -1; + } + if (string[1] == '+') { + mode &= ~(TCL_RDONLY|TCL_WRONLY); + mode |= TCL_RDWR; + if (string[2] != 0) { + goto error; + } + } else if (string[1] != 0) { + goto error; + } + return mode; + } + + /* + * The access modes are specified using a list of POSIX modes + * such as TCL_CREAT. + */ + + if (Tcl_SplitList(interp, string, &modeArgc, &modeArgv) != TCL_OK) { + if (interp != (Tcl_Interp *) NULL) { + Tcl_AddErrorInfo(interp, + "\n while processing open access modes \""); + Tcl_AddErrorInfo(interp, string); + Tcl_AddErrorInfo(interp, "\""); + } + return -1; + } + + gotRW = 0; + for (i = 0; i < modeArgc; i++) { + flag = modeArgv[i]; + c = flag[0]; + if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) { + mode = (mode & ~TCL_RW_MODES) | TCL_RDONLY; + gotRW = 1; + } else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) { + mode = (mode & ~TCL_RW_MODES) | TCL_WRONLY; + gotRW = 1; + } else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) { + mode = (mode & ~TCL_RW_MODES) | TCL_RDWR; + gotRW = 1; + } else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) { + mode |= TCL_ALWAYS_APPEND; + } else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) { + mode |= TCL_CREAT; + } else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) { + mode |= TCL_EXCL; + } else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) { + mode |= TCL_NOCTTY; + } else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) { + mode |= TCL_NONBLOCK; + } else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) { + mode |= TCL_TRUNC; + } else { + if (interp != (Tcl_Interp *) NULL) { + Tcl_AppendResult(interp, "invalid access mode \"", flag, + "\": must be RDONLY, WRONLY, RDWR, APPEND, CREAT", + " EXCL, NOCTTY, NONBLOCK, or TRUNC", (char *) NULL); + } + ckfree((char *) modeArgv); + return -1; + } + } + ckfree((char *) modeArgv); + if (!gotRW) { + if (interp != (Tcl_Interp *) NULL) { + Tcl_AppendResult(interp, "access mode must include either", + " RDONLY, WRONLY, or RDWR", (char *) NULL); + } + return -1; + } + return mode; +} diff --git a/tcl/mac/tclMacCommonPch.h b/tcl/mac/tclMacCommonPch.h new file mode 100644 index 00000000000..5f599ddabb0 --- /dev/null +++ b/tcl/mac/tclMacCommonPch.h @@ -0,0 +1,88 @@ +/* + * tclMacCommonPch.h -- + * + * Macintosh Tcl must be compiled with certain compiler options to + * ensure that it will work correctly. The following pragmas are + * used to ensure that those options are set correctly. An error + * will occur at compile time if they are not set correctly. + * + * Copyright (c) 1998 by Scriptics Corporation. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#if !__option(enumsalwaysint) +#error Tcl requires the Metrowerks setting "Enums always ints". +#endif + + +#if !defined(__POWERPC__) +#if !__option(far_data) +#error Tcl requires the Metrowerks setting "Far data". +#endif +#endif + + +#if !defined(__POWERPC__) +#if !__option(fourbyteints) +#error Tcl requires the Metrowerks setting "4 byte ints". +#endif +#endif + + +#if !defined(__POWERPC__) +#if !__option(IEEEdoubles) +#error Tcl requires the Metrowerks setting "8 byte doubles". +#endif +#endif + + +/* +* The define is used most everywhere to tell Tcl (or any Tcl +* extensions) that we are compiling for the Macintosh platform. +*/ + + +#define MAC_TCL + + +/* +* The following defines control the behavior of the Macintosh +* Universial Headers. +*/ + + +#define SystemSevenOrLater 1 +#define STRICT_CONTROLS 1 +#define STRICT_WINDOWS 1 + + +/* +* Define the following symbol if you want +* comprehensive debugging turned on. +*/ + + +/* #define TCL_DEBUG */ + + +#ifdef TCL_DEBUG +# define TCL_MEM_DEBUG +# define TCL_TEST +#endif + + + +/* +* For a while, we will continue to use the old routine names, so that +* people with older versions of CodeWarrior will still be able to compile +* the source (albeit they will have to update the project files themselves). +* +* At some point, we will convert over to the new routine names. +*/ + + +#define OLDROUTINENAMES 1 diff --git a/tcl/mac/tclMacDNR.c b/tcl/mac/tclMacDNR.c new file mode 100644 index 00000000000..1425bd224c4 --- /dev/null +++ b/tcl/mac/tclMacDNR.c @@ -0,0 +1,23 @@ +/* + * tclMacDNR.c + * + * This file actually just includes the file "dnr.c" provided by + * Apple Computer and redistributed by MetroWerks (and other compiler + * vendors.) Unfortunantly, despite various bug reports, dnr.c uses + * C++ style comments and will not compile under the "ANSI Strict" + * mode that the rest of Tcl compiles under. Furthermore, the Apple + * license prohibits me from redistributing a corrected version of + * dnr.c. This file uses a pragma to turn off the Strict ANSI option + * and then includes the dnr.c file. + * + * Copyright (c) 1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#pragma ANSI_strict off +#include <dnr.c> +#pragma ANSI_strict reset diff --git a/tcl/mac/tclMacEnv.c b/tcl/mac/tclMacEnv.c new file mode 100644 index 00000000000..30c159a33e8 --- /dev/null +++ b/tcl/mac/tclMacEnv.c @@ -0,0 +1,536 @@ +/* + * tclMacEnv.c -- + * + * Implements the "environment" on a Macintosh. + * + * Copyright (c) 1995-1996 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <Gestalt.h> +#include <Folders.h> +#include <TextUtils.h> +#include <Resources.h> +#include <string.h> + +#include "tcl.h" +#include "tclInt.h" +#include "tclMacInt.h" +#include "tclPort.h" + +#define kMaxEnvStringSize 255 +#define kMaxEnvVarSize 100 +#define kLoginnameTag "LOGIN=" +#define kUsernameTag "USER=" +#define kDefaultDirTag "HOME=" + +/* + * The following specifies a text file where additional environment variables + * can be set. The file must reside in the preferences folder. If the file + * doesn't exist NO error will occur. Commet out the difinition if you do + * NOT want to use an environment variables file. + */ +#define kPrefsFile "Tcl Environment Variables" + +/* + * The following specifies the Name of a 'STR#' resource in the application + * where additional environment variables may be set. If the resource doesn't + * exist no errors will occur. Commet it out if you don't want it. + */ +#define REZ_ENV "\pTcl Environment Variables" + +/* Globals */ +char **environ = NULL; + +/* + * Declarations for local procedures defined in this file: + */ +static char ** RezRCVariables _ANSI_ARGS_((void)); +static char ** FileRCVariables _ANSI_ARGS_((void)); +static char ** PathVariables _ANSI_ARGS_((void)); +static char ** SystemVariables _ANSI_ARGS_((void)); +static char * MakeFolderEnvVar _ANSI_ARGS_((char * prefixTag, + long whichFolder)); +static char * GetUserName _ANSI_ARGS_((void)); + +/* + *---------------------------------------------------------------------- + * + * RezRCVariables -- + * + * Creates environment variables from the applications resource fork. + * The function looks for the 'STR#' resource with the name defined + * in the #define REZ_ENV. If the define is not defined this code + * will not be included. If the resource doesn't exist or no strings + * reside in the resource nothing will happen. + * + * Results: + * ptr to value on success, NULL if error. + * + * Side effects: + * Memory is allocated and returned to the caller. + * + *---------------------------------------------------------------------- + */ + +#ifdef REZ_ENV +static char ** +RezRCVariables() +{ + Handle envStrs = NULL; + char** rezEnv = NULL; + short int numStrs; + + envStrs = GetNamedResource('STR#', REZ_ENV); + if (envStrs == NULL) return NULL; + numStrs = *((short *) (*envStrs)); + + rezEnv = (char **) ckalloc((numStrs + 1) * sizeof(char *)); + + if (envStrs != NULL) { + ResType theType; + Str255 theName; + short theID, index = 1; + int i = 0; + char* string; + + GetResInfo(envStrs, &theID, &theType, theName); + for(;;) { + GetIndString(theName, theID, index++); + if (theName[0] == '\0') break; + string = (char *) ckalloc(theName[0] + 2); + strncpy(string, (char *) theName + 1, theName[0]); + string[theName[0]] = '\0'; + rezEnv[i++] = string; + } + ReleaseResource(envStrs); + + rezEnv[i] = NULL; + return rezEnv; + } + + return NULL; +} +#endif + +/* + *---------------------------------------------------------------------- + * + * FileRCVariables -- + * + * Creates environment variables from a file in the system preferences + * folder. The function looks for a file in the preferences folder + * a name defined in the #define kPrefsFile. If the define is not + * defined this code will not be included. If the resource doesn't exist or + * no strings reside in the resource nothing will happen. + * + * Results: + * ptr to value on success, NULL if error. + * + * Side effects: + * Memory is allocated and returned to the caller. + * + *---------------------------------------------------------------------- + */ + +#ifdef kPrefsFile +static char ** +FileRCVariables() +{ + char *prefsFolder = NULL; + char *tempPtr = NULL; + char **fileEnv = NULL; + FILE *thePrefsFile = NULL; + int i; + FSSpec prefDir; + OSErr err; + Handle theString = NULL; + Tcl_Channel chan; + int size; + Tcl_DString lineRead; + + err = FSpFindFolder(kOnSystemDisk, kPreferencesFolderType, + kDontCreateFolder, &prefDir); + if (err != noErr) { + return NULL; + } + err = FSpPathFromLocation(&prefDir, &size, &theString); + if (err != noErr) { + return NULL; + } + (void) Munger(theString, size, NULL, 0, kPrefsFile, strlen(kPrefsFile)); + + HLock(theString); + chan = Tcl_OpenFileChannel(NULL, *theString, "r", 0); + HUnlock(theString); + DisposeHandle(theString); + if (chan == NULL) { + return NULL; + } + + /* + * We found a env file. Let start parsing it. + */ + fileEnv = (char **) ckalloc((kMaxEnvVarSize + 1) * sizeof(char *)); + + i = 0; + Tcl_DStringInit(&lineRead); + while (Tcl_Gets(chan, &lineRead) != -1) { + /* + * First strip off new line char + */ + if (lineRead.string[lineRead.length-1] == '\n') { + lineRead.string[lineRead.length-1] = '\0'; + } + if (lineRead.string[0] == '\0' || lineRead.string[0] == '#') { + /* + * skip empty lines or commented lines + */ + Tcl_DStringSetLength(&lineRead, 0); + continue; + } + + tempPtr = (char *) ckalloc(lineRead.length + 1); + strcpy(tempPtr, lineRead.string); + fileEnv[i++] = tempPtr; + Tcl_DStringSetLength(&lineRead, 0); + } + + fileEnv[i] = NULL; + Tcl_Close(NULL, chan); + Tcl_DStringFree(&lineRead); + + return fileEnv; +} +#endif + +/* + *---------------------------------------------------------------------- + * + * MakeFolderEnvVar -- + * + * This function creates "environment" variable by taking a prefix and + * appending a folder path to a directory. The directory is specified + * by a integer value acceptable by the FindFolder function. + * + * Results: + * The function returns an *allocated* string. If the folder doesn't + * exist the return string is still allocated and just contains the + * given prefix. + * + * Side effects: + * Memory is allocated and returned to the caller. + * + *---------------------------------------------------------------------- + */ + +static char * +MakeFolderEnvVar( + char * prefixTag, /* Prefix added before result. */ + long whichFolder) /* Constant for FSpFindFolder. */ +{ + char * thePath = NULL; + char * result = NULL; + OSErr theErr = noErr; + Handle theString = NULL; + FSSpec theFolder; + int size; + Tcl_DString pathStr; + Tcl_DString tagPathStr; + + Tcl_DStringInit(&pathStr); + theErr = FSpFindFolder(kOnSystemDisk, whichFolder, + kDontCreateFolder, &theFolder); + if (theErr == noErr) { + theErr = FSpPathFromLocation(&theFolder, &size, &theString); + + HLock(theString); + tclPlatform = TCL_PLATFORM_MAC; + Tcl_DStringAppend(&pathStr, *theString, -1); + HUnlock(theString); + DisposeHandle(theString); + + Tcl_DStringInit(&tagPathStr); + Tcl_DStringAppend(&tagPathStr, prefixTag, strlen(prefixTag)); + Tcl_DStringAppend(&tagPathStr, pathStr.string, pathStr.length); + Tcl_DStringFree(&pathStr); + + /* + * Make sure the path ends with a ':' + */ + if (tagPathStr.string[tagPathStr.length - 1] != ':') { + Tcl_DStringAppend(&tagPathStr, ":", 1); + } + + /* + * Don't free tagPathStr - rather make sure it's allocated + * and return it as the result. + */ + if (tagPathStr.string == tagPathStr.staticSpace) { + result = (char *) ckalloc(tagPathStr.length + 1); + strcpy(result, tagPathStr.string); + } else { + result = tagPathStr.string; + } + } else { + result = (char *) ckalloc(strlen(prefixTag) + 1); + strcpy(result, prefixTag); + } + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * PathVariables -- + * + * Creates environment variables from the system call FSpFindFolder. + * The function generates environment variables for many of the + * commonly used paths on the Macintosh. + * + * Results: + * ptr to value on success, NULL if error. + * + * Side effects: + * Memory is allocated and returned to the caller. + * + *---------------------------------------------------------------------- + */ + +static char ** +PathVariables() +{ + int i = 0; + char **sysEnv; + char *thePath = NULL; + + sysEnv = (char **) ckalloc((12) * sizeof(char *)); + + sysEnv[i++] = MakeFolderEnvVar("PREF_FOLDER=", kPreferencesFolderType); + sysEnv[i++] = MakeFolderEnvVar("SYS_FOLDER=", kSystemFolderType); + sysEnv[i++] = MakeFolderEnvVar("TEMP=", kTemporaryFolderType); + sysEnv[i++] = MakeFolderEnvVar("APPLE_M_FOLDER=", kAppleMenuFolderType); + sysEnv[i++] = MakeFolderEnvVar("CP_FOLDER=", kControlPanelFolderType); + sysEnv[i++] = MakeFolderEnvVar("DESK_FOLDER=", kDesktopFolderType); + sysEnv[i++] = MakeFolderEnvVar("EXT_FOLDER=", kExtensionFolderType); + sysEnv[i++] = MakeFolderEnvVar("PRINT_MON_FOLDER=", + kPrintMonitorDocsFolderType); + sysEnv[i++] = MakeFolderEnvVar("SHARED_TRASH_FOLDER=", + kWhereToEmptyTrashFolderType); + sysEnv[i++] = MakeFolderEnvVar("TRASH_FOLDER=", kTrashFolderType); + sysEnv[i++] = MakeFolderEnvVar("START_UP_FOLDER=", kStartupFolderType); + sysEnv[i++] = NULL; + + return sysEnv; +} + +/* + *---------------------------------------------------------------------- + * + * SystemVariables -- + * + * Creates environment variables from various Mac system calls. + * + * Results: + * ptr to value on success, NULL if error. + * + * Side effects: + * Memory is allocated and returned to the caller. + * + *---------------------------------------------------------------------- + */ + +static char ** +SystemVariables() +{ + int i = 0; + char ** sysEnv; + char * thePath = NULL; + Handle theString = NULL; + FSSpec currentDir; + int size; + + sysEnv = (char **) ckalloc((4) * sizeof(char *)); + + /* + * Get user name from chooser. It will be assigned to both + * the USER and LOGIN environment variables. + */ + thePath = GetUserName(); + if (thePath != NULL) { + sysEnv[i] = (char *) ckalloc(strlen(kLoginnameTag) + strlen(thePath) + 1); + strcpy(sysEnv[i], kLoginnameTag); + strcpy(sysEnv[i]+strlen(kLoginnameTag), thePath); + i++; + sysEnv[i] = (char *) ckalloc(strlen(kUsernameTag) + strlen(thePath) + 1); + strcpy(sysEnv[i], kUsernameTag); + strcpy(sysEnv[i]+strlen(kUsernameTag), thePath); + i++; + } + + /* + * Get 'home' directory + */ +#ifdef kDefaultDirTag + FSpGetDefaultDir(¤tDir); + FSpPathFromLocation(¤tDir, &size, &theString); + HLock(theString); + sysEnv[i] = (char *) ckalloc(strlen(kDefaultDirTag) + size + 4); + strcpy(sysEnv[i], kDefaultDirTag); + strncpy(sysEnv[i]+strlen(kDefaultDirTag) , *theString, size); + if (sysEnv[i][strlen(kDefaultDirTag) + size - 1] != ':') { + sysEnv[i][strlen(kDefaultDirTag) + size] = ':'; + sysEnv[i][strlen(kDefaultDirTag) + size + 1] = '\0'; + } else { + sysEnv[i][strlen(kDefaultDirTag) + size] = '\0'; + } + HUnlock(theString); + DisposeHandle(theString); + i++; +#endif + + sysEnv[i++] = NULL; + return sysEnv; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacCreateEnv -- + * + * This function allocates and populates the global "environ" + * variable. Entries are in traditional Unix format but variables + * are, hopefully, a bit more relevant for the Macintosh. + * + * Results: + * The number of elements in the newly created environ array. + * + * Side effects: + * Memory is allocated and pointed too by the environ variable. + * + *---------------------------------------------------------------------- + */ + +int +TclMacCreateEnv() +{ + char ** sysEnv = NULL; + char ** pathEnv = NULL; + char ** fileEnv = NULL; + char ** rezEnv = NULL; + int count = 0; + int i, j; + + sysEnv = SystemVariables(); + if (sysEnv != NULL) { + for (i = 0; sysEnv[i] != NULL; count++, i++) { + /* Empty Loop */ + } + } + + pathEnv = PathVariables(); + if (pathEnv != NULL) { + for (i = 0; pathEnv[i] != NULL; count++, i++) { + /* Empty Loop */ + } + } + +#ifdef kPrefsFile + fileEnv = FileRCVariables(); + if (fileEnv != NULL) { + for (i = 0; fileEnv[i] != NULL; count++, i++) { + /* Empty Loop */ + } + } +#endif + +#ifdef REZ_ENV + rezEnv = RezRCVariables(); + if (rezEnv != NULL) { + for (i = 0; rezEnv[i] != NULL; count++, i++) { + /* Empty Loop */ + } + } +#endif + + /* + * Create environ variable + */ + environ = (char **) ckalloc((count + 1) * sizeof(char *)); + j = 0; + + if (sysEnv != NULL) { + for (i = 0; sysEnv[i] != NULL;) + environ[j++] = sysEnv[i++]; + ckfree((char *) sysEnv); + } + + if (pathEnv != NULL) { + for (i = 0; pathEnv[i] != NULL;) + environ[j++] = pathEnv[i++]; + ckfree((char *) pathEnv); + } + +#ifdef kPrefsFile + if (fileEnv != NULL) { + for (i = 0; fileEnv[i] != NULL;) + environ[j++] = fileEnv[i++]; + ckfree((char *) fileEnv); + } +#endif + +#ifdef REZ_ENV + if (rezEnv != NULL) { + for (i = 0; rezEnv[i] != NULL;) + environ[j++] = rezEnv[i++]; + ckfree((char *) rezEnv); + } +#endif + + environ[j] = NULL; + return j; +} + +/* + *---------------------------------------------------------------------- + * + * GetUserName -- + * + * Get the user login name. + * + * Results: + * ptr to static string, NULL if error. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static char * +GetUserName() +{ + static char buf[33]; + short refnum; + Handle h; + + refnum = CurResFile(); + UseResFile(0); + h = GetResource('STR ', -16096); + UseResFile(refnum); + if (h == NULL) { + return NULL; + } + + HLock(h); + strncpy(buf, (*h)+1, **h); + buf[**h] = '\0'; + HUnlock(h); + ReleaseResource(h); + return(buf[0] ? buf : NULL); +} diff --git a/tcl/mac/tclMacExit.c b/tcl/mac/tclMacExit.c new file mode 100644 index 00000000000..ef1b9d38435 --- /dev/null +++ b/tcl/mac/tclMacExit.c @@ -0,0 +1,333 @@ +/* + * tclMacExit.c -- + * + * This file contains routines that deal with cleaning up various state + * when Tcl/Tk applications quit. Unfortunantly, not all state is cleaned + * up by the process when an application quites or crashes. Also you + * need to do different things depending on wether you are running as + * 68k code, PowerPC, or a code resource. The Exit handler code was + * adapted from code posted on alt.sources.mac by Dave Nebinger. + * + * Copyright (c) 1995 Dave Nebinger. + * Copyright (c) 1995-1996 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tclInt.h" +#include "tclMacInt.h" +#include <SegLoad.h> +#include <Traps.h> +#include <Processes.h> + +/* + * Various typedefs and defines needed to patch ExitToShell. + */ + +enum { + uppExitToShellProcInfo = kPascalStackBased +}; + +#if GENERATINGCFM +typedef UniversalProcPtr ExitToShellUPP; + +#define CallExitToShellProc(userRoutine) \ + CallUniversalProc((UniversalProcPtr)(userRoutine),uppExitToShellProcInfo) +#define NewExitToShellProc(userRoutine) \ + (ExitToShellUPP)NewRoutineDescriptor((ProcPtr)(userRoutine), \ + uppExitToShellProcInfo, GetCurrentArchitecture()) + +#else +typedef ExitToShellProcPtr ExitToShellUPP; + +#define CallExitToShellProc(userRoutine) \ + (*(userRoutine))() +#define NewExitToShellProc(userRoutine) \ + (ExitToShellUPP)(userRoutine) +#endif + +#define DisposeExitToShellProc(userRoutine) \ + DisposeRoutineDescriptor(userRoutine) + +#if defined(powerc)||defined(__powerc) +#pragma options align=mac68k +#endif +struct ExitToShellUPPList{ + struct ExitToShellUPPList* nextProc; + ExitToShellUPP userProc; +}; +#if defined(powerc)||defined(__powerc) +#pragma options align=reset +#endif + +typedef struct ExitToShellDataStruct ExitToShellDataRec,* ExitToShellDataPtr,** ExitToShellDataHdl; + +typedef struct ExitToShellUPPList ExitToShellUPPList,* ExitToShellUPPListPtr,** ExitToShellUPPHdl; + +#if defined(powerc)||defined(__powerc) +#pragma options align=mac68k +#endif +struct ExitToShellDataStruct{ + unsigned long a5; + ExitToShellUPPList* userProcs; + ExitToShellUPP oldProc; +}; +#if defined(powerc)||defined(__powerc) +#pragma options align=reset +#endif + +/* + * Static globals used within this file. + */ +static ExitToShellDataPtr gExitToShellData = (ExitToShellDataPtr) NULL; + + +/* + *---------------------------------------------------------------------- + * + * TclPlatformExit -- + * + * This procedure implements the Macintosh specific exit routine. + * We explicitly callthe ExitHandler function to do various clean + * up. + * + * Results: + * None. + * + * Side effects: + * We exit the process. + * + *---------------------------------------------------------------------- + */ + +void +TclPlatformExit( + int status) /* Ignored. */ +{ + TclMacExitHandler(); + +/* + * If we are using the Metrowerks Standard Library, then we will call its exit so that it + * will get a chance to clean up temp files, and so forth. It always calls the standard + * ExitToShell, so the Tcl handlers will also get called. + * + * If you have another exit, make sure that it does not patch ExitToShell, and does + * call it. If so, it will probably work as well. + * + */ + +#ifdef __MSL__ + exit(status); +#else + ExitToShell(); +#endif + +} + +/* + *---------------------------------------------------------------------- + * + * TclMacExitHandler -- + * + * This procedure is invoked after Tcl at the last possible moment + * to clean up any state Tcl has left around that may cause other + * applications to crash. For example, this function can be used + * as the termination routine for CFM applications. + * + * Results: + * None. + * + * Side effects: + * Various cleanup occurs. + * + *---------------------------------------------------------------------- + */ + +void +TclMacExitHandler() +{ + ExitToShellUPPListPtr curProc; + + /* + * Loop through all installed Exit handlers + * and call them. Always make sure we are in + * a clean state in case we are recursivly called. + */ + if ((gExitToShellData) != NULL && (gExitToShellData->userProcs != NULL)){ + + /* + * Call the installed exit to shell routines. + */ + curProc = gExitToShellData->userProcs; + do { + gExitToShellData->userProcs = curProc->nextProc; + CallExitToShellProc(curProc->userProc); + DisposeExitToShellProc(curProc->userProc); + DisposePtr((Ptr) curProc); + curProc = gExitToShellData->userProcs; + } while (curProc != (ExitToShellUPPListPtr) NULL); + } + + return; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacInstallExitToShellPatch -- + * + * This procedure installs a way to clean up state at the latest + * possible moment before we exit. These are things that must + * be cleaned up or the system will crash. The exact way in which + * this is implemented depends on the architecture in which we are + * running. For 68k applications we patch the ExitToShell call. + * For PowerPC applications we just create a list of procs to call. + * The function ExitHandler should be installed in the Code + * Fragments terminiation routine. + * + * Results: + * None. + * + * Side effects: + * Installs the new routine. + * + *---------------------------------------------------------------------- + */ + +OSErr +TclMacInstallExitToShellPatch( + ExitToShellProcPtr newProc) /* Function pointer. */ +{ + ExitToShellUPP exitHandler; + ExitToShellUPPListPtr listPtr; + + if (gExitToShellData == (ExitToShellDataPtr) NULL){ + TclMacInitExitToShell(true); + } + + /* + * Add the passed in function pointer to the list of functions + * to be called when ExitToShell is called. + */ + exitHandler = NewExitToShellProc(newProc); + listPtr = (ExitToShellUPPListPtr) NewPtrClear(sizeof(ExitToShellUPPList)); + listPtr->userProc = exitHandler; + listPtr->nextProc = gExitToShellData->userProcs; + gExitToShellData->userProcs = listPtr; + + return noErr; +} + +/* + *---------------------------------------------------------------------- + * + * ExitToShellPatchRoutine -- + * + * This procedure is invoked when someone calls ExitToShell for + * this application. This function performs some last miniute + * clean up and then calls the real ExitToShell routine. + * + * Results: + * None. + * + * Side effects: + * Various cleanup occurs. + * + *---------------------------------------------------------------------- + */ + +static pascal void +ExitToShellPatchRoutine() +{ + ExitToShellUPP oldETS; + long oldA5; + + /* + * Set up our A5 world. This allows us to have + * access to our global variables in the 68k world. + */ + oldA5 = SetCurrentA5(); + SetA5(gExitToShellData->a5); + + /* + * Call the function that invokes all + * of the handlers. + */ + TclMacExitHandler(); + + /* + * Call the origional ExitToShell routine. + */ + oldETS = gExitToShellData->oldProc; + DisposePtr((Ptr) gExitToShellData); + SetA5(oldA5); + CallExitToShellProc(oldETS); + return; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacInitExitToShell -- + * + * This procedure initializes the ExitToShell clean up machanism. + * Generally, this is handled automatically when users make a call + * to InstallExitToShellPatch. However, it can be called + * explicitly at startup time to turn off the patching mechanism. + * This can be used by code resources which could be removed from + * the application before ExitToShell is called. + * + * Note, if we are running from CFM code we never install the + * patch. Instead, the function ExitHandler should be installed + * as the terminiation routine for the code fragment. + * + * Results: + * None. + * + * Side effects: + * Creates global state. + * + *---------------------------------------------------------------------- + */ + +void +TclMacInitExitToShell( + int usePatch) /* True if on 68k. */ +{ + if (gExitToShellData == (ExitToShellDataPtr) NULL){ +#if GENERATINGCFM + gExitToShellData = (ExitToShellDataPtr) + NewPtr(sizeof(ExitToShellDataRec)); + gExitToShellData->a5 = SetCurrentA5(); + gExitToShellData->userProcs = (ExitToShellUPPList*) NULL; +#else + ExitToShellUPP oldExitToShell, newExitToShellPatch; + short exitToShellTrap; + + /* + * Initialize patch mechanism. + */ + + gExitToShellData = (ExitToShellDataPtr) NewPtr(sizeof(ExitToShellDataRec)); + gExitToShellData->a5 = SetCurrentA5(); + gExitToShellData->userProcs = (ExitToShellUPPList*) NULL; + + /* + * Save state needed to call origional ExitToShell routine. Install + * the new ExitToShell code in it's place. + */ + if (usePatch) { + exitToShellTrap = _ExitToShell & 0x3ff; + newExitToShellPatch = NewExitToShellProc(ExitToShellPatchRoutine); + oldExitToShell = (ExitToShellUPP) + NGetTrapAddress(exitToShellTrap, ToolTrap); + NSetTrapAddress((UniversalProcPtr) newExitToShellPatch, + exitToShellTrap, ToolTrap); + gExitToShellData->oldProc = oldExitToShell; + } +#endif + } +} diff --git a/tcl/mac/tclMacFCmd.c b/tcl/mac/tclMacFCmd.c new file mode 100644 index 00000000000..21f725dc451 --- /dev/null +++ b/tcl/mac/tclMacFCmd.c @@ -0,0 +1,1408 @@ +/* + * tclMacFCmd.c -- + * + * Implements the Macintosh specific portions of the file manipulation + * subcommands of the "file" command. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tclInt.h" +#include "tclMac.h" +#include "tclMacInt.h" +#include "tclPort.h" +#include <FSpCompat.h> +#include <MoreFilesExtras.h> +#include <Strings.h> +#include <Errors.h> +#include <FileCopy.h> +#include <DirectoryCopy.h> +#include <Script.h> +#include <string.h> +#include <Finder.h> + +/* + * Callback for the file attributes code. + */ + +static int GetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp, + int objIndex, char *fileName, + Tcl_Obj **attributePtrPtr)); +static int GetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp, + int objIndex, char *fileName, + Tcl_Obj **readOnlyPtrPtr)); +static int SetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp, + int objIndex, char *fileName, + Tcl_Obj *attributePtr)); +static int SetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp, + int objIndex, char *fileName, + Tcl_Obj *readOnlyPtr)); + +/* + * These are indeces into the tclpFileAttrsStrings table below. + */ + +#define MAC_CREATOR_ATTRIBUTE 0 +#define MAC_HIDDEN_ATTRIBUTE 1 +#define MAC_READONLY_ATTRIBUTE 2 +#define MAC_TYPE_ATTRIBUTE 3 + +/* + * Global variables for the file attributes code. + */ + +char *tclpFileAttrStrings[] = {"-creator", "-hidden", "-readonly", + "-type", (char *) NULL}; +CONST TclFileAttrProcs tclpFileAttrProcs[] = { + {GetFileFinderAttributes, SetFileFinderAttributes}, + {GetFileFinderAttributes, SetFileFinderAttributes}, + {GetFileReadOnly, SetFileReadOnly}, + {GetFileFinderAttributes, SetFileFinderAttributes}}; + + +/* + * Prototypes for procedure only used in this file + */ + +static pascal Boolean CopyErrHandler _ANSI_ARGS_((OSErr error, + short failedOperation, + short srcVRefNum, long srcDirID, + StringPtr srcName, short dstVRefNum, + long dstDirID,StringPtr dstName)); +OSErr FSpGetFLockCompat _ANSI_ARGS_((const FSSpec *specPtr, + Boolean *lockedPtr)); +static OSErr GenerateUniqueName _ANSI_ARGS_((short vRefNum, + long dirID1, long dirID2, Str31 uniqueName)); +static OSErr GetFileSpecs _ANSI_ARGS_((char *path, FSSpec *pathSpecPtr, + FSSpec *dirSpecPtr, Boolean *pathExistsPtr, + Boolean *pathIsDirectoryPtr)); +static OSErr MoveRename _ANSI_ARGS_((const FSSpec *srcSpecPtr, + const FSSpec *dstSpecPtr, StringPtr copyName)); +static int Pstrequal _ANSI_ARGS_((ConstStr255Param stringA, + ConstStr255Param stringB)); + +/* + *--------------------------------------------------------------------------- + * + * TclpRenameFile -- + * + * Changes the name of an existing file or directory, from src to dst. + * If src and dst refer to the same file or directory, does nothing + * and returns success. Otherwise if dst already exists, it will be + * deleted and replaced by src subject to the following conditions: + * If src is a directory, dst may be an empty directory. + * If src is a file, dst may be a file. + * In any other situation where dst already exists, the rename will + * fail. + * + * Results: + * If the directory was successfully created, returns TCL_OK. + * Otherwise the return value is TCL_ERROR and errno is set to + * indicate the error. Some possible values for errno are: + * + * EACCES: src or dst parent directory can't be read and/or written. + * EEXIST: dst is a non-empty directory. + * EINVAL: src is a root directory or dst is a subdirectory of src. + * EISDIR: dst is a directory, but src is not. + * ENOENT: src doesn't exist. src or dst is "". + * ENOTDIR: src is a directory, but dst is not. + * EXDEV: src and dst are on different filesystems. + * + * Side effects: + * The implementation of rename may allow cross-filesystem renames, + * but the caller should be prepared to emulate it with copy and + * delete if errno is EXDEV. + * + *--------------------------------------------------------------------------- + */ + +int +TclpRenameFile( + char *src, /* Pathname of file or dir to be renamed. */ + char *dst) /* New pathname for file or directory. */ +{ + FSSpec srcFileSpec, dstFileSpec, dstDirSpec; + OSErr err; + long srcID, dummy; + Boolean srcIsDirectory, dstIsDirectory, dstExists, dstLocked; + + err = FSpLocationFromPath(strlen(src), src, &srcFileSpec); + if (err == noErr) { + FSpGetDirectoryID(&srcFileSpec, &srcID, &srcIsDirectory); + } + if (err == noErr) { + err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists, + &dstIsDirectory); + } + if (err == noErr) { + if (dstExists == 0) { + err = MoveRename(&srcFileSpec, &dstDirSpec, dstFileSpec.name); + goto end; + } + err = FSpGetFLockCompat(&dstFileSpec, &dstLocked); + if (dstLocked) { + FSpRstFLockCompat(&dstFileSpec); + } + } + if (err == noErr) { + if (srcIsDirectory) { + if (dstIsDirectory) { + /* + * The following call will remove an empty directory. If it + * fails, it's because it wasn't empty. + */ + + if (TclpRemoveDirectory(dst, 0, NULL) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Now that that empty directory is gone, we can try + * renaming src. If that fails, we'll put this empty + * directory back, for completeness. + */ + + err = MoveRename(&srcFileSpec, &dstDirSpec, dstFileSpec.name); + if (err != noErr) { + FSpDirCreateCompat(&dstFileSpec, smSystemScript, &dummy); + if (dstLocked) { + FSpSetFLockCompat(&dstFileSpec); + } + } + } else { + errno = ENOTDIR; + return TCL_ERROR; + } + } else { + if (dstIsDirectory) { + errno = EISDIR; + return TCL_ERROR; + } else { + /* + * Overwrite existing file by: + * + * 1. Rename existing file to temp name. + * 2. Rename old file to new name. + * 3. If success, delete temp file. If failure, + * put temp file back to old name. + */ + + Str31 tmpName; + FSSpec tmpFileSpec; + + err = GenerateUniqueName(dstFileSpec.vRefNum, + dstFileSpec.parID, dstFileSpec.parID, tmpName); + if (err == noErr) { + err = FSpRenameCompat(&dstFileSpec, tmpName); + } + if (err == noErr) { + err = FSMakeFSSpecCompat(dstFileSpec.vRefNum, + dstFileSpec.parID, tmpName, &tmpFileSpec); + } + if (err == noErr) { + err = MoveRename(&srcFileSpec, &dstDirSpec, + dstFileSpec.name); + } + if (err == noErr) { + FSpDeleteCompat(&tmpFileSpec); + } else { + FSpDeleteCompat(&dstFileSpec); + FSpRenameCompat(&tmpFileSpec, dstFileSpec.name); + if (dstLocked) { + FSpSetFLockCompat(&dstFileSpec); + } + } + } + } + } + + end: + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpCopyFile -- + * + * Copy a single file (not a directory). If dst already exists and + * is not a directory, it is removed. + * + * Results: + * If the file was successfully copied, returns TCL_OK. Otherwise + * the return value is TCL_ERROR and errno is set to indicate the + * error. Some possible values for errno are: + * + * EACCES: src or dst parent directory can't be read and/or written. + * EISDIR: src or dst is a directory. + * ENOENT: src doesn't exist. src or dst is "". + * + * Side effects: + * This procedure will also copy symbolic links, block, and + * character devices, and fifos. For symbolic links, the links + * themselves will be copied and not what they point to. For the + * other special file types, the directory entry will be copied and + * not the contents of the device that it refers to. + * + *--------------------------------------------------------------------------- + */ + +int +TclpCopyFile( + char *src, /* Pathname of file to be copied. */ + char *dst) /* Pathname of file to copy to. */ +{ + OSErr err, dstErr; + Boolean dstExists, dstIsDirectory, dstLocked; + FSSpec srcFileSpec, dstFileSpec, dstDirSpec, tmpFileSpec; + Str31 tmpName; + + err = FSpLocationFromPath(strlen(src), src, &srcFileSpec); + if (err == noErr) { + err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists, + &dstIsDirectory); + } + if (dstExists) { + if (dstIsDirectory) { + errno = EISDIR; + return TCL_ERROR; + } + err = FSpGetFLockCompat(&dstFileSpec, &dstLocked); + if (dstLocked) { + FSpRstFLockCompat(&dstFileSpec); + } + + /* + * Backup dest file. + */ + + dstErr = GenerateUniqueName(dstFileSpec.vRefNum, dstFileSpec.parID, + dstFileSpec.parID, tmpName); + if (dstErr == noErr) { + dstErr = FSpRenameCompat(&dstFileSpec, tmpName); + } + } + if (err == noErr) { + err = FSpFileCopy(&srcFileSpec, &dstDirSpec, + (StringPtr) dstFileSpec.name, NULL, 0, true); + } + if ((dstExists != false) && (dstErr == noErr)) { + FSMakeFSSpecCompat(dstFileSpec.vRefNum, dstFileSpec.parID, + tmpName, &tmpFileSpec); + if (err == noErr) { + /* + * Delete backup file. + */ + + FSpDeleteCompat(&tmpFileSpec); + } else { + + /* + * Restore backup file. + */ + + FSpDeleteCompat(&dstFileSpec); + FSpRenameCompat(&tmpFileSpec, dstFileSpec.name); + if (dstLocked) { + FSpSetFLockCompat(&dstFileSpec); + } + } + } + + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpDeleteFile -- + * + * Removes a single file (not a directory). + * + * Results: + * If the file was successfully deleted, returns TCL_OK. Otherwise + * the return value is TCL_ERROR and errno is set to indicate the + * error. Some possible values for errno are: + * + * EACCES: a parent directory can't be read and/or written. + * EISDIR: path is a directory. + * ENOENT: path doesn't exist or is "". + * + * Side effects: + * The file is deleted, even if it is read-only. + * + *--------------------------------------------------------------------------- + */ + +int +TclpDeleteFile( + char *path) /* Pathname of file to be removed. */ +{ + OSErr err; + FSSpec fileSpec; + Boolean isDirectory; + long dirID; + + err = FSpLocationFromPath(strlen(path), path, &fileSpec); + if (err == noErr) { + /* + * Since FSpDeleteCompat will delete an empty directory, make sure + * that this isn't a directory first. + */ + + FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); + if (isDirectory == true) { + errno = EISDIR; + return TCL_ERROR; + } + } + err = FSpDeleteCompat(&fileSpec); + if (err == fLckdErr) { + FSpRstFLockCompat(&fileSpec); + err = FSpDeleteCompat(&fileSpec); + if (err != noErr) { + FSpSetFLockCompat(&fileSpec); + } + } + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpCreateDirectory -- + * + * Creates the specified directory. All parent directories of the + * specified directory must already exist. The directory is + * automatically created with permissions so that user can access + * the new directory and create new files or subdirectories in it. + * + * Results: + * If the directory was successfully created, returns TCL_OK. + * Otherwise the return value is TCL_ERROR and errno is set to + * indicate the error. Some possible values for errno are: + * + * EACCES: a parent directory can't be read and/or written. + * EEXIST: path already exists. + * ENOENT: a parent directory doesn't exist. + * + * Side effects: + * A directory is created with the current umask, except that + * permission for u+rwx will always be added. + * + *--------------------------------------------------------------------------- + */ + +int +TclpCreateDirectory( + char *path) /* Pathname of directory to create. */ +{ + OSErr err; + FSSpec dirSpec; + long outDirID; + + err = FSpLocationFromPath(strlen(path), path, &dirSpec); + if (err == noErr) { + err = dupFNErr; /* EEXIST. */ + } else if (err == fnfErr) { + err = FSpDirCreateCompat(&dirSpec, smSystemScript, &outDirID); + } + + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpCopyDirectory -- + * + * Recursively copies a directory. The target directory dst must + * not already exist. Note that this function does not merge two + * directory hierarchies, even if the target directory is an an + * empty directory. + * + * Results: + * If the directory was successfully copied, returns TCL_OK. + * Otherwise the return value is TCL_ERROR, errno is set to indicate + * the error, and the pathname of the file that caused the error + * is stored in errorPtr. See TclpCreateDirectory and TclpCopyFile + * for a description of possible values for errno. + * + * Side effects: + * An exact copy of the directory hierarchy src will be created + * with the name dst. If an error occurs, the error will + * be returned immediately, and remaining files will not be + * processed. + * + *--------------------------------------------------------------------------- + */ + +int +TclpCopyDirectory( + char *src, /* Pathname of directory to be copied. */ + char *dst, /* Pathname of target directory. */ + Tcl_DString *errorPtr) /* If non-NULL, initialized DString for + * error reporting. */ +{ + OSErr err, saveErr; + long srcID, tmpDirID; + FSSpec srcFileSpec, dstFileSpec, dstDirSpec, tmpDirSpec, tmpFileSpec; + Boolean srcIsDirectory, srcLocked; + Boolean dstIsDirectory, dstExists; + Str31 tmpName; + + err = FSpLocationFromPath(strlen(src), src, &srcFileSpec); + if (err == noErr) { + err = FSpGetDirectoryID(&srcFileSpec, &srcID, &srcIsDirectory); + } + if (err == noErr) { + if (srcIsDirectory == false) { + err = afpObjectTypeErr; /* ENOTDIR. */ + } + } + if (err == noErr) { + err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists, + &dstIsDirectory); + } + if (dstExists) { + if (dstIsDirectory == false) { + err = afpObjectTypeErr; /* ENOTDIR. */ + } else { + err = dupFNErr; /* EEXIST. */ + } + } + if (err != noErr) { + goto done; + } + if ((srcFileSpec.vRefNum == dstFileSpec.vRefNum) && + (srcFileSpec.parID == dstFileSpec.parID) && + (Pstrequal(srcFileSpec.name, dstFileSpec.name) != 0)) { + /* + * Copying on top of self. No-op. + */ + + goto done; + } + + /* + * This algorthm will work making a copy of the source directory in + * the current directory with a new name, in a new directory with the + * same name, and in a new directory with a new name: + * + * 1. Make dstDir/tmpDir. + * 2. Copy srcDir/src to dstDir/tmpDir/src + * 3. Rename dstDir/tmpDir/src to dstDir/tmpDir/dst (if necessary). + * 4. CatMove dstDir/tmpDir/dst to dstDir/dst. + * 5. Remove dstDir/tmpDir. + */ + + err = FSpGetFLockCompat(&srcFileSpec, &srcLocked); + if (srcLocked) { + FSpRstFLockCompat(&srcFileSpec); + } + if (err == noErr) { + err = GenerateUniqueName(dstFileSpec.vRefNum, dstFileSpec.parID, + dstFileSpec.parID, tmpName); + } + if (err == noErr) { + FSMakeFSSpecCompat(dstFileSpec.vRefNum, dstFileSpec.parID, + tmpName, &tmpDirSpec); + err = FSpDirCreateCompat(&tmpDirSpec, smSystemScript, &tmpDirID); + } + if (err == noErr) { + err = FSpDirectoryCopy(&srcFileSpec, &tmpDirSpec, NULL, 0, true, + CopyErrHandler); + } + + /* + * Even if the Copy failed, Rename/Move whatever did get copied to the + * appropriate final destination, if possible. + */ + + saveErr = err; + err = noErr; + if (Pstrequal(srcFileSpec.name, dstFileSpec.name) == 0) { + err = FSMakeFSSpecCompat(tmpDirSpec.vRefNum, tmpDirID, + srcFileSpec.name, &tmpFileSpec); + if (err == noErr) { + err = FSpRenameCompat(&tmpFileSpec, dstFileSpec.name); + } + } + if (err == noErr) { + err = FSMakeFSSpecCompat(tmpDirSpec.vRefNum, tmpDirID, + dstFileSpec.name, &tmpFileSpec); + } + if (err == noErr) { + err = FSpCatMoveCompat(&tmpFileSpec, &dstDirSpec); + } + if (err == noErr) { + if (srcLocked) { + FSpSetFLockCompat(&dstFileSpec); + } + } + + FSpDeleteCompat(&tmpDirSpec); + + if (saveErr != noErr) { + err = saveErr; + } + + done: + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + if (errorPtr != NULL) { + Tcl_DStringAppend(errorPtr, dst, -1); + } + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * CopyErrHandler -- + * + * This procedure is called from the MoreFiles procedure + * FSpDirectoryCopy whenever an error occurs. + * + * Results: + * False if the condition should not be considered an error, true + * otherwise. + * + * Side effects: + * Since FSpDirectoryCopy() is called only after removing any + * existing target directories, there shouldn't be any errors. + * + *---------------------------------------------------------------------- + */ + +static pascal Boolean +CopyErrHandler( + OSErr error, /* Error that occured */ + short failedOperation, /* operation that caused the error */ + short srcVRefNum, /* volume ref number of source */ + long srcDirID, /* directory id of source */ + StringPtr srcName, /* name of source */ + short dstVRefNum, /* volume ref number of dst */ + long dstDirID, /* directory id of dst */ + StringPtr dstName) /* name of dst directory */ +{ + return true; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpRemoveDirectory -- + * + * Removes directory (and its contents, if the recursive flag is set). + * + * Results: + * If the directory was successfully removed, returns TCL_OK. + * Otherwise the return value is TCL_ERROR, errno is set to indicate + * the error, and the pathname of the file that caused the error + * is stored in errorPtr. Some possible values for errno are: + * + * EACCES: path directory can't be read and/or written. + * EEXIST: path is a non-empty directory. + * EINVAL: path is a root directory. + * ENOENT: path doesn't exist or is "". + * ENOTDIR: path is not a directory. + * + * Side effects: + * Directory removed. If an error occurs, the error will be returned + * immediately, and remaining files will not be deleted. + * + *--------------------------------------------------------------------------- + */ + +int +TclpRemoveDirectory( + char *path, /* Pathname of directory to be removed. */ + int recursive, /* If non-zero, removes directories that + * are nonempty. Otherwise, will only remove + * empty directories. */ + Tcl_DString *errorPtr) /* If non-NULL, initialized DString for + * error reporting. */ +{ + OSErr err; + FSSpec fileSpec; + long dirID; + int locked; + Boolean isDirectory; + CInfoPBRec pb; + Str255 fileName; + + locked = 0; + err = FSpLocationFromPath(strlen(path), path, &fileSpec); + if (err != noErr) { + goto done; + } + + /* + * Since FSpDeleteCompat will delete a file, make sure this isn't + * a file first. + */ + + isDirectory = 1; + FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); + if (isDirectory == 0) { + errno = ENOTDIR; + return TCL_ERROR; + } + + err = FSpDeleteCompat(&fileSpec); + if (err == fLckdErr) { + locked = 1; + FSpRstFLockCompat(&fileSpec); + err = FSpDeleteCompat(&fileSpec); + } + if (err == noErr) { + return TCL_OK; + } + if (err != fBsyErr) { + goto done; + } + + if (recursive == 0) { + /* + * fBsyErr means one of three things: file busy, directory not empty, + * or working directory control block open. Determine if directory + * is empty. If directory is not empty, return EEXIST. + */ + + pb.hFileInfo.ioVRefNum = fileSpec.vRefNum; + pb.hFileInfo.ioDirID = dirID; + pb.hFileInfo.ioNamePtr = (StringPtr) fileName; + pb.hFileInfo.ioFDirIndex = 1; + if (PBGetCatInfoSync(&pb) == noErr) { + err = dupFNErr; /* EEXIST */ + goto done; + } + } + + /* + * DeleteDirectory removes a directory and all its contents, including + * any locked files. There is no interface to get the name of the + * file that caused the error, if an error occurs deleting this tree, + * unless we rewrite DeleteDirectory ourselves. + */ + + err = DeleteDirectory(fileSpec.vRefNum, dirID, NULL); + + done: + if (err != noErr) { + if (errorPtr != NULL) { + Tcl_DStringAppend(errorPtr, path, -1); + } + if (locked) { + FSpSetFLockCompat(&fileSpec); + } + errno = TclMacOSErrorToPosixError(err); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------------------- + * + * MoveRename -- + * + * Helper function for TclpRenameFile. Renames a file or directory + * into the same directory or another directory. The target name + * must not already exist in the destination directory. + * + * Don't use FSpMoveRenameCompat because it doesn't work with + * directories or with locked files. + * + * Results: + * Returns a mac error indicating the cause of the failure. + * + * Side effects: + * Creates a temp file in the target directory to handle a rename + * between directories. + * + *-------------------------------------------------------------------------- + */ + +static OSErr +MoveRename( + const FSSpec *srcFileSpecPtr, /* Source object. */ + const FSSpec *dstDirSpecPtr, /* Destination directory. */ + StringPtr copyName) /* New name for object in destination + * directory. */ +{ + OSErr err; + long srcID, dstID; + Boolean srcIsDir, dstIsDir; + Str31 tmpName; + FSSpec dstFileSpec, srcDirSpec, tmpSrcFileSpec, tmpDstFileSpec; + Boolean locked; + + if (srcFileSpecPtr->parID == 1) { + /* + * Trying to rename a volume. + */ + + return badMovErr; + } + if (srcFileSpecPtr->vRefNum != dstDirSpecPtr->vRefNum) { + /* + * Renaming across volumes. + */ + + return diffVolErr; + } + err = FSpGetFLockCompat(srcFileSpecPtr, &locked); + if (locked) { + FSpRstFLockCompat(srcFileSpecPtr); + } + if (err == noErr) { + err = FSpGetDirectoryID(dstDirSpecPtr, &dstID, &dstIsDir); + } + if (err == noErr) { + if (srcFileSpecPtr->parID == dstID) { + /* + * Renaming object within directory. + */ + + err = FSpRenameCompat(srcFileSpecPtr, copyName); + goto done; + } + if (Pstrequal(srcFileSpecPtr->name, copyName)) { + /* + * Moving object to another directory (under same name). + */ + + err = FSpCatMoveCompat(srcFileSpecPtr, dstDirSpecPtr); + goto done; + } + err = FSpGetDirectoryID(srcFileSpecPtr, &srcID, &srcIsDir); + } + if (err == noErr) { + /* + * Fullblown: rename source object to temp name, move temp to + * dest directory, and rename temp to target. + */ + + err = GenerateUniqueName(srcFileSpecPtr->vRefNum, + srcFileSpecPtr->parID, dstID, tmpName); + FSMakeFSSpecCompat(srcFileSpecPtr->vRefNum, srcFileSpecPtr->parID, + tmpName, &tmpSrcFileSpec); + FSMakeFSSpecCompat(dstDirSpecPtr->vRefNum, dstID, tmpName, + &tmpDstFileSpec); + } + if (err == noErr) { + err = FSpRenameCompat(srcFileSpecPtr, tmpName); + } + if (err == noErr) { + err = FSpCatMoveCompat(&tmpSrcFileSpec, dstDirSpecPtr); + if (err == noErr) { + err = FSpRenameCompat(&tmpDstFileSpec, copyName); + if (err == noErr) { + goto done; + } + FSMakeFSSpecCompat(srcFileSpecPtr->vRefNum, srcFileSpecPtr->parID, + NULL, &srcDirSpec); + FSpCatMoveCompat(&tmpDstFileSpec, &srcDirSpec); + } + FSpRenameCompat(&tmpSrcFileSpec, srcFileSpecPtr->name); + } + + done: + if (locked != false) { + if (err == noErr) { + FSMakeFSSpecCompat(dstDirSpecPtr->vRefNum, + dstID, copyName, &dstFileSpec); + FSpSetFLockCompat(&dstFileSpec); + } else { + FSpSetFLockCompat(srcFileSpecPtr); + } + } + return err; +} + +/* + *--------------------------------------------------------------------------- + * + * GetFileSpecs -- + * + * Generate a filename that is not in either of the two specified + * directories (on the same volume). + * + * Results: + * Standard macintosh error. On success, uniqueName is filled with + * the name of the temporary file. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static OSErr +GenerateUniqueName( + short vRefNum, /* Volume on which the following directories + * are located. */ + long dirID1, /* ID of first directory. */ + long dirID2, /* ID of second directory. May be the same + * as the first. */ + Str31 uniqueName) /* Filled with filename for a file that is + * not located in either of the above two + * directories. */ +{ + OSErr err; + long i; + CInfoPBRec pb; + static unsigned char hexStr[16] = "0123456789ABCDEF"; + static long startSeed = 248923489; + + pb.hFileInfo.ioVRefNum = vRefNum; + pb.hFileInfo.ioFDirIndex = 0; + pb.hFileInfo.ioNamePtr = uniqueName; + + while (1) { + startSeed++; + pb.hFileInfo.ioNamePtr[0] = 8; + for (i = 1; i <= 8; i++) { + pb.hFileInfo.ioNamePtr[i] = hexStr[((startSeed >> ((8-i)*4)) & 0xf)]; + } + pb.hFileInfo.ioDirID = dirID1; + err = PBGetCatInfoSync(&pb); + if (err == fnfErr) { + if (dirID1 != dirID2) { + pb.hFileInfo.ioDirID = dirID2; + err = PBGetCatInfoSync(&pb); + } + if (err == fnfErr) { + return noErr; + } + } + if (err == noErr) { + continue; + } + return err; + } +} + +/* + *--------------------------------------------------------------------------- + * + * GetFileSpecs -- + * + * Gets FSSpecs for the specified path and its parent directory. + * + * Results: + * The return value is noErr if there was no error getting FSSpecs, + * otherwise it is an error describing the problem. Fills buffers + * with information, as above. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static OSErr +GetFileSpecs( + char *path, /* The path to query. */ + FSSpec *pathSpecPtr, /* Filled with information about path. */ + FSSpec *dirSpecPtr, /* Filled with information about path's + * parent directory. */ + Boolean *pathExistsPtr, /* Set to true if path actually exists, + * false if it doesn't or there was an + * error reading the specified path. */ + Boolean *pathIsDirectoryPtr)/* Set to true if path is itself a directory, + * otherwise false. */ +{ + char *dirName; + OSErr err; + int argc; + char **argv; + long d; + Tcl_DString buffer; + + *pathExistsPtr = false; + *pathIsDirectoryPtr = false; + + Tcl_DStringInit(&buffer); + Tcl_SplitPath(path, &argc, &argv); + if (argc == 1) { + dirName = ":"; + } else { + dirName = Tcl_JoinPath(argc - 1, argv, &buffer); + } + err = FSpLocationFromPath(strlen(dirName), dirName, dirSpecPtr); + Tcl_DStringFree(&buffer); + ckfree((char *) argv); + + if (err == noErr) { + err = FSpLocationFromPath(strlen(path), path, pathSpecPtr); + if (err == noErr) { + *pathExistsPtr = true; + err = FSpGetDirectoryID(pathSpecPtr, &d, pathIsDirectoryPtr); + } else if (err == fnfErr) { + err = noErr; + } + } + return err; +} + +/* + *------------------------------------------------------------------------- + * + * FSpGetFLockCompat -- + * + * Determines if there exists a software lock on the specified + * file. The software lock could prevent the file from being + * renamed or moved. + * + * Results: + * Standard macintosh error code. + * + * Side effects: + * None. + * + * + *------------------------------------------------------------------------- + */ + +OSErr +FSpGetFLockCompat( + const FSSpec *specPtr, /* File to query. */ + Boolean *lockedPtr) /* Set to true if file is locked, false + * if it isn't or there was an error reading + * specified file. */ +{ + CInfoPBRec pb; + OSErr err; + + pb.hFileInfo.ioVRefNum = specPtr->vRefNum; + pb.hFileInfo.ioDirID = specPtr->parID; + pb.hFileInfo.ioNamePtr = (StringPtr) specPtr->name; + pb.hFileInfo.ioFDirIndex = 0; + + err = PBGetCatInfoSync(&pb); + if ((err == noErr) && (pb.hFileInfo.ioFlAttrib & 0x01)) { + *lockedPtr = true; + } else { + *lockedPtr = false; + } + return err; +} + +/* + *---------------------------------------------------------------------- + * + * Pstrequal -- + * + * Pascal string compare. + * + * Results: + * Returns 1 if strings equal, 0 otherwise. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +Pstrequal ( + ConstStr255Param stringA, /* Pascal string A */ + ConstStr255Param stringB) /* Pascal string B */ +{ + int i, len; + + len = *stringA; + for (i = 0; i <= len; i++) { + if (*stringA++ != *stringB++) { + return 0; + } + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * GetFileFinderAttributes -- + * + * Returns a Tcl_Obj containing the value of a file attribute + * which is part of the FInfo record. Which attribute is controlled + * by objIndex. + * + * Results: + * Returns a standard TCL error. If the return value is TCL_OK, + * the new creator or file type object is put into attributePtrPtr. + * The object will have ref count 0. If there is an error, + * attributePtrPtr is not touched. + * + * Side effects: + * A new object is allocated if the file is valid. + * + *---------------------------------------------------------------------- + */ + +static int +GetFileFinderAttributes( + Tcl_Interp *interp, /* The interp to report errors with. */ + int objIndex, /* The index of the attribute option. */ + char *fileName, /* The name of the file. */ + Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ +{ + OSErr err; + FSSpec fileSpec; + FInfo finfo; + + err = FSpLocationFromPath(strlen(fileName), fileName, &fileSpec); + + if (err == noErr) { + err = FSpGetFInfo(&fileSpec, &finfo); + } + + if (err == noErr) { + switch (objIndex) { + case MAC_CREATOR_ATTRIBUTE: + *attributePtrPtr = Tcl_NewOSTypeObj(finfo.fdCreator); + break; + case MAC_HIDDEN_ATTRIBUTE: + *attributePtrPtr = Tcl_NewBooleanObj(finfo.fdFlags + & kIsInvisible); + break; + case MAC_TYPE_ATTRIBUTE: + *attributePtrPtr = Tcl_NewOSTypeObj(finfo.fdType); + break; + } + } else if (err == fnfErr) { + long dirID; + Boolean isDirectory = 0; + + err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); + if ((err == noErr) && isDirectory) { + if (objIndex == MAC_HIDDEN_ATTRIBUTE) { + *attributePtrPtr = Tcl_NewBooleanObj(0); + } else { + *attributePtrPtr = Tcl_NewOSTypeObj('Fldr'); + } + } + } + + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "couldn't get attributes for file \"", fileName, "\": ", + Tcl_PosixError(interp), (char *) NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GetFileReadOnly -- + * + * Returns a Tcl_Obj containing a Boolean value indicating whether + * or not the file is read-only. The object will have ref count 0. + * This procedure just checks the Finder attributes; it does not + * check AppleShare sharing attributes. + * + * Results: + * Returns a standard TCL error. If the return value is TCL_OK, + * the new creator type object is put into readOnlyPtrPtr. + * If there is an error, readOnlyPtrPtr is not touched. + * + * Side effects: + * A new object is allocated if the file is valid. + * + *---------------------------------------------------------------------- + */ + +static int +GetFileReadOnly( + Tcl_Interp *interp, /* The interp to report errors with. */ + int objIndex, /* The index of the attribute. */ + char *fileName, /* The name of the file. */ + Tcl_Obj **readOnlyPtrPtr) /* A pointer to return the object with. */ +{ + OSErr err; + FSSpec fileSpec; + CInfoPBRec paramBlock; + + err = FSpLocationFromPath(strlen(fileName), fileName, &fileSpec); + + if (err == noErr) { + if (err == noErr) { + paramBlock.hFileInfo.ioCompletion = NULL; + paramBlock.hFileInfo.ioNamePtr = fileSpec.name; + paramBlock.hFileInfo.ioVRefNum = fileSpec.vRefNum; + paramBlock.hFileInfo.ioFDirIndex = 0; + paramBlock.hFileInfo.ioDirID = fileSpec.parID; + err = PBGetCatInfo(¶mBlock, 0); + if (err == noErr) { + + /* + * For some unknown reason, the Mac does not give + * symbols for the bits in the ioFlAttrib field. + * 1 -> locked. + */ + + *readOnlyPtrPtr = Tcl_NewBooleanObj( + paramBlock.hFileInfo.ioFlAttrib & 1); + } + } + } + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "couldn't get attributes for file \"", fileName, "\": ", + Tcl_PosixError(interp), (char *) NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * SetFileFinderAttributes -- + * + * Sets the file to the creator or file type given by attributePtr. + * objIndex determines whether the creator or file type is set. + * + * Results: + * Returns a standard TCL error. + * + * Side effects: + * The file's attribute is set. + * + *---------------------------------------------------------------------- + */ + +static int +SetFileFinderAttributes( + Tcl_Interp *interp, /* The interp to report errors with. */ + int objIndex, /* The index of the attribute. */ + char *fileName, /* The name of the file. */ + Tcl_Obj *attributePtr) /* The command line object. */ +{ + OSErr err; + FSSpec fileSpec; + FInfo finfo; + + err = FSpLocationFromPath(strlen(fileName), fileName, &fileSpec); + + if (err == noErr) { + err = FSpGetFInfo(&fileSpec, &finfo); + } + + if (err == noErr) { + switch (objIndex) { + case MAC_CREATOR_ATTRIBUTE: + if (Tcl_GetOSTypeFromObj(interp, attributePtr, + &finfo.fdCreator) != TCL_OK) { + return TCL_ERROR; + } + break; + case MAC_HIDDEN_ATTRIBUTE: { + int hidden; + + if (Tcl_GetBooleanFromObj(interp, attributePtr, &hidden) + != TCL_OK) { + return TCL_ERROR; + } + if (hidden) { + finfo.fdFlags |= kIsInvisible; + } else { + finfo.fdFlags &= ~kIsInvisible; + } + break; + } + case MAC_TYPE_ATTRIBUTE: + if (Tcl_GetOSTypeFromObj(interp, attributePtr, + &finfo.fdType) != TCL_OK) { + return TCL_ERROR; + } + break; + } + err = FSpSetFInfo(&fileSpec, &finfo); + } else if (err == fnfErr) { + long dirID; + Boolean isDirectory = 0; + + err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); + if ((err == noErr) && isDirectory) { + Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); + Tcl_AppendStringsToObj(resultPtr, "cannot set ", + tclpFileAttrStrings[objIndex], ": \"", + fileName, "\" is a directory", (char *) NULL); + return TCL_ERROR; + } + } + + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "couldn't set attributes for file \"", fileName, "\": ", + Tcl_PosixError(interp), (char *) NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * SetFileReadOnly -- + * + * Sets the file to be read-only according to the Boolean value + * given by hiddenPtr. + * + * Results: + * Returns a standard TCL error. + * + * Side effects: + * The file's attribute is set. + * + *---------------------------------------------------------------------- + */ + +static int +SetFileReadOnly( + Tcl_Interp *interp, /* The interp to report errors with. */ + int objIndex, /* The index of the attribute. */ + char *fileName, /* The name of the file. */ + Tcl_Obj *readOnlyPtr) /* The command line object. */ +{ + OSErr err; + FSSpec fileSpec; + HParamBlockRec paramBlock; + int hidden; + + err = FSpLocationFromPath(strlen(fileName), fileName, &fileSpec); + + if (err == noErr) { + if (Tcl_GetBooleanFromObj(interp, readOnlyPtr, &hidden) != TCL_OK) { + return TCL_ERROR; + } + + paramBlock.fileParam.ioCompletion = NULL; + paramBlock.fileParam.ioNamePtr = fileSpec.name; + paramBlock.fileParam.ioVRefNum = fileSpec.vRefNum; + paramBlock.fileParam.ioDirID = fileSpec.parID; + if (hidden) { + err = PBHSetFLock(¶mBlock, 0); + } else { + err = PBHRstFLock(¶mBlock, 0); + } + } + + if (err == fnfErr) { + long dirID; + Boolean isDirectory = 0; + err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); + if ((err == noErr) && isDirectory) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "cannot set a directory to read-only when File Sharing is turned off", + (char *) NULL); + return TCL_ERROR; + } else { + err = fnfErr; + } + } + + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "couldn't set attributes for file \"", fileName, "\": ", + Tcl_PosixError(interp), (char *) NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpListVolumes -- + * + * Lists the currently mounted volumes + * + * Results: + * A standard Tcl result. Will always be TCL_OK, since there is no way + * that this command can fail. Also, the interpreter's result is set to + * the list of volumes. + * + * Side effects: + * None + * + *--------------------------------------------------------------------------- + */ + +int +TclpListVolumes( + Tcl_Interp *interp) /* Interpreter to which to pass the volume list */ +{ + HParamBlockRec pb; + Str255 name; + OSErr theError = noErr; + Tcl_Obj *resultPtr, *elemPtr; + short volIndex = 1; + + resultPtr = Tcl_NewObj(); + + /* + * We use two facts: + * 1) The Mac volumes are enumerated by the ioVolIndex parameter of + * the HParamBlockRec. They run through the integers contiguously, + * starting at 1. + * 2) PBHGetVInfoSync returns an error when you ask for a volume index + * that does not exist. + * + */ + + while ( 1 ) { + pb.volumeParam.ioNamePtr = (StringPtr) & name; + pb.volumeParam.ioVolIndex = volIndex; + + theError = PBHGetVInfoSync(&pb); + + if ( theError != noErr ) { + break; + } + + elemPtr = Tcl_NewStringObj((char *) name + 1, (int) name[0]); + Tcl_AppendToObj(elemPtr, ":", 1); + Tcl_ListObjAppendElement(interp, resultPtr, elemPtr); + + volIndex++; + } + + Tcl_SetObjResult(interp, resultPtr); + return TCL_OK; +} + diff --git a/tcl/mac/tclMacFile.c b/tcl/mac/tclMacFile.c new file mode 100644 index 00000000000..60a602eb5c9 --- /dev/null +++ b/tcl/mac/tclMacFile.c @@ -0,0 +1,840 @@ +/* + * tclMacFile.c -- + * + * This file implements the channel drivers for Macintosh + * files. It also comtains Macintosh version of other Tcl + * functions that deal with the file system. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +/* + * Note: This code eventually needs to support async I/O. In doing this + * we will need to keep track of all current async I/O. If exit to shell + * is called - we shouldn't exit until all asyc I/O completes. + */ + +#include "tclInt.h" +#include "tclPort.h" +#include "tclMacInt.h" +#include <Aliases.h> +#include <Errors.h> +#include <Processes.h> +#include <Strings.h> +#include <Types.h> +#include <MoreFiles.h> +#include <MoreFilesExtras.h> +#include <FSpCompat.h> + +/* + * Static variables used by the TclpStat function. + */ +static int initalized = false; +static long gmt_offset; + +/* + * The variable below caches the name of the current working directory + * in order to avoid repeated calls to getcwd. The string is malloc-ed. + * NULL means the cache needs to be refreshed. + */ + +static char *currentDir = NULL; + +/* + *---------------------------------------------------------------------- + * + * TclChdir -- + * + * Change the current working directory. + * + * Results: + * The result is a standard Tcl result. If an error occurs and + * interp isn't NULL, an error message is left in interp->result. + * + * Side effects: + * The working directory for this application is changed. Also + * the cache maintained used by TclGetCwd is deallocated and + * set to NULL. + * + *---------------------------------------------------------------------- + */ + +int +TclChdir( + Tcl_Interp *interp, /* If non NULL, used for error reporting. */ + char *dirName) /* Path to new working directory. */ +{ + FSSpec spec; + OSErr err; + Boolean isFolder; + long dirID; + + if (currentDir != NULL) { + ckfree(currentDir); + currentDir = NULL; + } + + err = FSpLocationFromPath(strlen(dirName), dirName, &spec); + if (err != noErr) { + errno = ENOENT; + goto chdirError; + } + + err = FSpGetDirectoryID(&spec, &dirID, &isFolder); + if (err != noErr) { + errno = ENOENT; + goto chdirError; + } + + if (isFolder != true) { + errno = ENOTDIR; + goto chdirError; + } + + err = FSpSetDefaultDir(&spec); + if (err != noErr) { + switch (err) { + case afpAccessDenied: + errno = EACCES; + break; + default: + errno = ENOENT; + } + goto chdirError; + } + + return TCL_OK; + chdirError: + if (interp != NULL) { + Tcl_AppendResult(interp, "couldn't change working directory to \"", + dirName, "\": ", Tcl_PosixError(interp), (char *) NULL); + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * TclGetCwd -- + * + * Return the path name of the current working directory. + * + * Results: + * The result is the full path name of the current working + * directory, or NULL if an error occurred while figuring it + * out. If an error occurs and interp isn't NULL, an error + * message is left in interp->result. + * + * Side effects: + * The path name is cached to avoid having to recompute it + * on future calls; if it is already cached, the cached + * value is returned. + * + *---------------------------------------------------------------------- + */ + +char * +TclGetCwd( + Tcl_Interp *interp) /* If non NULL, used for error reporting. */ +{ + FSSpec theSpec; + int length; + Handle pathHandle = NULL; + + if (currentDir == NULL) { + if (FSpGetDefaultDir(&theSpec) != noErr) { + if (interp != NULL) { + interp->result = "error getting working directory name"; + } + return NULL; + } + if (FSpPathFromLocation(&theSpec, &length, &pathHandle) != noErr) { + if (interp != NULL) { + interp->result = "error getting working directory name"; + } + return NULL; + } + HLock(pathHandle); + currentDir = (char *) ckalloc((unsigned) (length + 1)); + strcpy(currentDir, *pathHandle); + HUnlock(pathHandle); + DisposeHandle(pathHandle); + } + return currentDir; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_WaitPid -- + * + * Fakes a call to wait pid. + * + * Results: + * Always returns -1. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Pid +Tcl_WaitPid( + Tcl_Pid pid, + int *statPtr, + int options) +{ + return (Tcl_Pid) -1; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_FindExecutable -- + * + * This procedure computes the absolute path name of the current + * application, given its argv[0] value. However, this + * implementation doesn't use of need the argv[0] value. NULL + * may be passed in its place. + * + * Results: + * None. + * + * Side effects: + * The variable tclExecutableName gets filled in with the file + * name for the application, if we figured it out. If we couldn't + * figure it out, Tcl_FindExecutable is set to NULL. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_FindExecutable( + char *argv0) /* The value of the application's argv[0]. */ +{ + ProcessSerialNumber psn; + ProcessInfoRec info; + Str63 appName; + FSSpec fileSpec; + int pathLength; + Handle pathName = NULL; + OSErr err; + + GetCurrentProcess(&psn); + info.processInfoLength = sizeof(ProcessInfoRec); + info.processName = appName; + info.processAppSpec = &fileSpec; + GetProcessInformation(&psn, &info); + + if (tclExecutableName != NULL) { + ckfree(tclExecutableName); + tclExecutableName = NULL; + } + + err = FSpPathFromLocation(&fileSpec, &pathLength, &pathName); + + tclExecutableName = (char *) ckalloc((unsigned) pathLength + 1); + HLock(pathName); + strcpy(tclExecutableName, *pathName); + HUnlock(pathName); + DisposeHandle(pathName); +} + +/* + *---------------------------------------------------------------------- + * + * TclGetUserHome -- + * + * This function takes the passed in user name and finds the + * corresponding home directory specified in the password file. + * + * Results: + * On a Macintosh we always return a NULL. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +char * +TclGetUserHome( + char *name, /* User name to use to find home directory. */ + Tcl_DString *bufferPtr) /* May be used to hold result. Must not hold + * anything at the time of the call, and need + * not even be initialized. */ +{ + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * TclMatchFiles -- + * + * This routine is used by the globbing code to search a + * directory for all files which match a given pattern. + * + * Results: + * If the tail argument is NULL, then the matching files are + * added to the interp->result. Otherwise, TclDoGlob is called + * recursively for each matching subdirectory. The return value + * is a standard Tcl result indicating whether an error occurred + * in globbing. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- */ + +int +TclMatchFiles( + Tcl_Interp *interp, /* Interpreter to receive results. */ + char *separators, /* Directory separators to pass to TclDoGlob. */ + Tcl_DString *dirPtr, /* Contains path to directory to search. */ + char *pattern, /* Pattern to match against. */ + char *tail) /* Pointer to end of pattern. Tail must + * point to a location in pattern. */ +{ + char *dirName, *patternEnd = tail; + char savedChar; + int result = TCL_OK; + int baseLength = Tcl_DStringLength(dirPtr); + CInfoPBRec pb; + OSErr err; + FSSpec dirSpec; + Boolean isDirectory; + long dirID; + short itemIndex; + Str255 fileName; + + + /* + * Make sure that the directory part of the name really is a + * directory. + */ + + dirName = dirPtr->string; + FSpLocationFromPath(strlen(dirName), dirName, &dirSpec); + err = FSpGetDirectoryID(&dirSpec, &dirID, &isDirectory); + if ((err != noErr) || !isDirectory) { + return TCL_OK; + } + + /* + * Now open the directory for reading and iterate over the contents. + */ + + pb.hFileInfo.ioVRefNum = dirSpec.vRefNum; + pb.hFileInfo.ioDirID = dirID; + pb.hFileInfo.ioNamePtr = (StringPtr) fileName; + pb.hFileInfo.ioFDirIndex = itemIndex = 1; + + /* + * Clean up the end of the pattern and the tail pointer. Leave + * the tail pointing to the first character after the path separator + * following the pattern, or NULL. Also, ensure that the pattern + * is null-terminated. + */ + + if (*tail == '\\') { + tail++; + } + if (*tail == '\0') { + tail = NULL; + } else { + tail++; + } + savedChar = *patternEnd; + *patternEnd = '\0'; + + while (1) { + pb.hFileInfo.ioFDirIndex = itemIndex; + pb.hFileInfo.ioDirID = dirID; + err = PBGetCatInfoSync(&pb); + if (err != noErr) { + break; + } + + /* + * Now check to see if the file matches. If there are more + * characters to be processed, then ensure matching files are + * directories before calling TclDoGlob. Otherwise, just add + * the file to the result. + */ + + p2cstr(fileName); + if (Tcl_StringMatch((char *) fileName, pattern)) { + Tcl_DStringSetLength(dirPtr, baseLength); + Tcl_DStringAppend(dirPtr, (char *) fileName, -1); + if (tail == NULL) { + if ((dirPtr->length > 1) && + (strchr(dirPtr->string+1, ':') == NULL)) { + Tcl_AppendElement(interp, dirPtr->string+1); + } else { + Tcl_AppendElement(interp, dirPtr->string); + } + } else if ((pb.hFileInfo.ioFlAttrib & ioDirMask) != 0) { + Tcl_DStringAppend(dirPtr, ":", 1); + result = TclDoGlob(interp, separators, dirPtr, tail); + if (result != TCL_OK) { + break; + } + } + } + + itemIndex++; + } + *patternEnd = savedChar; + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclpStat -- + * + * This function replaces the library version of stat. The stat + * function provided by most Mac compiliers is rather broken and + * incomplete. + * + * Results: + * See stat documentation. + * + * Side effects: + * See stat documentation. + * + *---------------------------------------------------------------------- + */ + +int +TclpStat( + CONST char *path, + struct stat *buf) +{ + HFileInfo fpb; + HVolumeParam vpb; + OSErr err; + FSSpec fileSpec; + Boolean isDirectory; + long dirID; + + err = FSpLocationFromPath(strlen(path), path, &fileSpec); + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + return -1; + } + + /* + * Fill the fpb & vpb struct up with info about file or directory. + */ + + FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); + vpb.ioVRefNum = fpb.ioVRefNum = fileSpec.vRefNum; + vpb.ioNamePtr = fpb.ioNamePtr = fileSpec.name; + if (isDirectory) { + fpb.ioDirID = fileSpec.parID; + } else { + fpb.ioDirID = dirID; + } + + fpb.ioFDirIndex = 0; + err = PBGetCatInfoSync((CInfoPBPtr)&fpb); + if (err == noErr) { + vpb.ioVolIndex = 0; + err = PBHGetVInfoSync((HParmBlkPtr)&vpb); + if (err == noErr && buf != NULL) { + /* + * Files are always readable by everyone. + */ + + buf->st_mode = S_IRUSR | S_IRGRP | S_IROTH; + + /* + * Use the Volume Info & File Info to fill out stat buf. + */ + if (fpb.ioFlAttrib & 0x10) { + buf->st_mode |= S_IFDIR; + buf->st_nlink = 2; + } else { + buf->st_nlink = 1; + if (fpb.ioFlFndrInfo.fdFlags & 0x8000) { + buf->st_mode |= S_IFLNK; + } else { + buf->st_mode |= S_IFREG; + } + } + if ((fpb.ioFlAttrib & 0x10) || (fpb.ioFlFndrInfo.fdType == 'APPL')) { + /* + * Directories and applications are executable by everyone. + */ + + buf->st_mode |= S_IXUSR | S_IXGRP | S_IXOTH; + } + if ((fpb.ioFlAttrib & 0x01) == 0){ + /* + * If not locked, then everyone has write acces. + */ + + buf->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; + } + buf->st_ino = fpb.ioDirID; + buf->st_dev = fpb.ioVRefNum; + buf->st_uid = -1; + buf->st_gid = -1; + buf->st_rdev = 0; + buf->st_size = fpb.ioFlLgLen; + buf->st_blksize = vpb.ioVAlBlkSiz; + buf->st_blocks = (buf->st_size + buf->st_blksize - 1) + / buf->st_blksize; + + /* + * The times returned by the Mac file system are in the + * local time zone. We convert them to GMT so that the + * epoch starts from GMT. This is also consistant with + * what is returned from "clock seconds". + */ + if (initalized == false) { + MachineLocation loc; + + ReadLocation(&loc); + gmt_offset = loc.u.gmtDelta & 0x00ffffff; + if (gmt_offset & 0x00800000) { + gmt_offset = gmt_offset | 0xff000000; + } + initalized = true; + } + buf->st_atime = buf->st_mtime = fpb.ioFlMdDat - gmt_offset; + buf->st_ctime = fpb.ioFlCrDat - gmt_offset; + + } + } + + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + } + + return (err == noErr ? 0 : -1); +} + +/* + *---------------------------------------------------------------------- + * + * TclMacReadlink -- + * + * This function replaces the library version of readlink. + * + * Results: + * See readlink documentation. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclMacReadlink( + char *path, + char *buf, + int size) +{ + HFileInfo fpb; + OSErr err; + FSSpec fileSpec; + Boolean isDirectory; + Boolean wasAlias; + long dirID; + char fileName[256]; + char *end; + Handle theString = NULL; + int pathSize; + + /* + * Remove ending colons if they exist. + */ + while ((strlen(path) != 0) && (path[strlen(path) - 1] == ':')) { + path[strlen(path) - 1] = NULL; + } + + if (strchr(path, ':') == NULL) { + strcpy(fileName, path); + path = NULL; + } else { + end = strrchr(path, ':') + 1; + strcpy(fileName, end); + *end = NULL; + } + c2pstr(fileName); + + /* + * Create the file spec for the directory of the file + * we want to look at. + */ + if (path != NULL) { + err = FSpLocationFromPath(strlen(path), path, &fileSpec); + if (err != noErr) { + errno = EINVAL; + return -1; + } + } else { + FSMakeFSSpecCompat(0, 0, NULL, &fileSpec); + } + + /* + * Fill the fpb struct up with info about file or directory. + */ + FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); + fpb.ioVRefNum = fileSpec.vRefNum; + fpb.ioDirID = dirID; + fpb.ioNamePtr = (StringPtr) fileName; + + fpb.ioFDirIndex = 0; + err = PBGetCatInfoSync((CInfoPBPtr)&fpb); + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + return -1; + } else { + if (fpb.ioFlAttrib & 0x10) { + errno = EINVAL; + return -1; + } else { + if (fpb.ioFlFndrInfo.fdFlags & 0x8000) { + /* + * The file is a link! + */ + } else { + errno = EINVAL; + return -1; + } + } + } + + /* + * If we are here it's really a link - now find out + * where it points to. + */ + err = FSMakeFSSpecCompat(fileSpec.vRefNum, dirID, (StringPtr) fileName, &fileSpec); + if (err == noErr) { + err = ResolveAliasFile(&fileSpec, true, &isDirectory, &wasAlias); + } + if ((err == fnfErr) || wasAlias) { + err = FSpPathFromLocation(&fileSpec, &pathSize, &theString); + if ((err != noErr) || (pathSize > size)) { + DisposeHandle(theString); + errno = ENAMETOOLONG; + return -1; + } + } else { + errno = EINVAL; + return -1; + } + + strncpy(buf, *theString, pathSize); + DisposeHandle(theString); + + return pathSize; +} + +/* + *---------------------------------------------------------------------- + * + * TclpAccess -- + * + * This function replaces the library version of access. The + * access function provided by most Mac compiliers is rather + * broken or incomplete. + * + * Results: + * See access documentation. + * + * Side effects: + * See access documentation. + * + *---------------------------------------------------------------------- + */ + +int +TclpAccess( + const char *path, + int mode) +{ + HFileInfo fpb; + HVolumeParam vpb; + OSErr err; + FSSpec fileSpec; + Boolean isDirectory; + long dirID; + int full_mode = 0; + + err = FSpLocationFromPath(strlen(path), (char *) path, &fileSpec); + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + return -1; + } + + /* + * Fill the fpb & vpb struct up with info about file or directory. + */ + FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); + vpb.ioVRefNum = fpb.ioVRefNum = fileSpec.vRefNum; + vpb.ioNamePtr = fpb.ioNamePtr = fileSpec.name; + if (isDirectory) { + fpb.ioDirID = fileSpec.parID; + } else { + fpb.ioDirID = dirID; + } + + fpb.ioFDirIndex = 0; + err = PBGetCatInfoSync((CInfoPBPtr)&fpb); + if (err == noErr) { + vpb.ioVolIndex = 0; + err = PBHGetVInfoSync((HParmBlkPtr)&vpb); + if (err == noErr) { + /* + * Use the Volume Info & File Info to determine + * access information. If we have got this far + * we know the directory is searchable or the file + * exists. (We have F_OK) + */ + + /* + * Check to see if the volume is hardware or + * software locked. If so we arn't W_OK. + */ + if (mode & W_OK) { + if ((vpb.ioVAtrb & 0x0080) || (vpb.ioVAtrb & 0x8000)) { + errno = EROFS; + return -1; + } + if (fpb.ioFlAttrib & 0x01) { + errno = EACCES; + return -1; + } + } + + /* + * Directories are always searchable and executable. But only + * files of type 'APPL' are executable. + */ + if (!(fpb.ioFlAttrib & 0x10) && (mode & X_OK) + && (fpb.ioFlFndrInfo.fdType != 'APPL')) { + return -1; + } + } + } + + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + return -1; + } + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacFOpenHack -- + * + * This function replaces fopen. It supports paths with alises. + * Note, remember to undefine the fopen macro! + * + * Results: + * See fopen documentation. + * + * Side effects: + * See fopen documentation. + * + *---------------------------------------------------------------------- + */ + +#undef fopen +FILE * +TclMacFOpenHack( + const char *path, + const char *mode) +{ + OSErr err; + FSSpec fileSpec; + Handle pathString = NULL; + int size; + FILE * f; + + err = FSpLocationFromPath(strlen(path), (char *) path, &fileSpec); + if ((err != noErr) && (err != fnfErr)) { + return NULL; + } + err = FSpPathFromLocation(&fileSpec, &size, &pathString); + if ((err != noErr) && (err != fnfErr)) { + return NULL; + } + + HLock(pathString); + f = fopen(*pathString, mode); + HUnlock(pathString); + DisposeHandle(pathString); + return f; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacOSErrorToPosixError -- + * + * Given a Macintosh OSErr return the appropiate POSIX error. + * + * Results: + * A Posix error. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclMacOSErrorToPosixError( + int error) /* A Macintosh error. */ +{ + switch (error) { + case noErr: + return 0; + case bdNamErr: + return ENAMETOOLONG; + case afpObjectTypeErr: + return ENOTDIR; + case fnfErr: + case dirNFErr: + return ENOENT; + case dupFNErr: + return EEXIST; + case dirFulErr: + case dskFulErr: + return ENOSPC; + case fBsyErr: + return EBUSY; + case tmfoErr: + return ENFILE; + case fLckdErr: + case permErr: + case afpAccessDenied: + return EACCES; + case wPrErr: + case vLckdErr: + return EROFS; + case badMovErr: + return EINVAL; + case diffVolErr: + return EXDEV; + default: + return EINVAL; + } +} diff --git a/tcl/mac/tclMacInit.c b/tcl/mac/tclMacInit.c new file mode 100644 index 00000000000..218ca12a156 --- /dev/null +++ b/tcl/mac/tclMacInit.c @@ -0,0 +1,284 @@ +/* + * tclMacInit.c -- + * + * Contains the Mac-specific interpreter initialization functions. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <Files.h> +#include <Gestalt.h> +#include <TextUtils.h> +#include <Resources.h> +#include <Strings.h> +#include "tclInt.h" +#include "tclMacInt.h" + +/* + *---------------------------------------------------------------------- + * + * TclPlatformInit -- + * + * Performs Mac-specific interpreter initialization related to the + * tcl_platform and tcl_library variables. + * + * Results: + * None. + * + * Side effects: + * Sets "tcl_library" & "tcl_platfrom" Tcl variable + * + *---------------------------------------------------------------------- + */ + +void +TclPlatformInit( + Tcl_Interp *interp) /* Tcl interpreter to initialize. */ +{ + char *libDir; + Tcl_DString path, libPath; + long int gestaltResult; + int minor, major; + char versStr[10]; + + /* + * Set runtime C variable that tells cross platform C functions + * what platform they are running on. This can change at + * runtime for testing purposes. + */ + tclPlatform = TCL_PLATFORM_MAC; + + /* + * Define the tcl_platfrom variable. + */ + Tcl_SetVar2(interp, "tcl_platform", "platform", "macintosh", + TCL_GLOBAL_ONLY); + Tcl_SetVar2(interp, "tcl_platform", "os", "MacOS", TCL_GLOBAL_ONLY); + Gestalt(gestaltSystemVersion, &gestaltResult); + major = (gestaltResult & 0x0000FF00) >> 8; + minor = (gestaltResult & 0x000000F0) >> 4; + sprintf(versStr, "%d.%d", major, minor); + Tcl_SetVar2(interp, "tcl_platform", "osVersion", versStr, TCL_GLOBAL_ONLY); +#if GENERATINGPOWERPC + Tcl_SetVar2(interp, "tcl_platform", "machine", "ppc", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "tcl_platform", "machine", "68k", TCL_GLOBAL_ONLY); +#endif + + /* + * The tcl_library path can be found in one of two places. As an element + * in the env array. Or the default which is to a folder in side the + * Extensions folder of your system. + */ + + Tcl_DStringInit(&path); + libDir = Tcl_GetVar2(interp, "env", "TCL_LIBRARY", TCL_GLOBAL_ONLY); + if (libDir != NULL) { + Tcl_SetVar(interp, "tcl_library", libDir, TCL_GLOBAL_ONLY); + } else { + libDir = Tcl_GetVar2(interp, "env", "EXT_FOLDER", TCL_GLOBAL_ONLY); + if (libDir != NULL) { + Tcl_JoinPath(1, &libDir, &path); + + Tcl_DStringInit(&libPath); + Tcl_DStringAppend(&libPath, ":Tool Command Language:tcl", -1); + Tcl_DStringAppend(&libPath, TCL_VERSION, -1); + Tcl_JoinPath(1, &libPath.string, &path); + Tcl_DStringFree(&libPath); + Tcl_SetVar(interp, "tcl_library", path.string, TCL_GLOBAL_ONLY); + } else { + Tcl_SetVar(interp, "tcl_library", "no library", TCL_GLOBAL_ONLY); + } + } + + /* + * Now create the tcl_pkgPath variable. + */ + Tcl_DStringSetLength(&path, 0); + libDir = Tcl_GetVar2(interp, "env", "EXT_FOLDER", TCL_GLOBAL_ONLY); + if (libDir != NULL) { + Tcl_JoinPath(1, &libDir, &path); + libDir = ":Tool Command Language:"; + Tcl_JoinPath(1, &libDir, &path); + Tcl_SetVar(interp, "tcl_pkgPath", path.string, + TCL_GLOBAL_ONLY|TCL_LIST_ELEMENT); + } else { + Tcl_SetVar(interp, "tcl_pkgPath", "no extension folder", + TCL_GLOBAL_ONLY|TCL_LIST_ELEMENT); + } + Tcl_DStringFree(&path); +} + +/* + *---------------------------------------------------------------------- + * + * TclpCheckStackSpace -- + * + * On a 68K Mac, we can detect if we are about to blow the stack. + * Called before an evaluation can happen when nesting depth is + * checked. + * + * Results: + * 1 if there is enough stack space to continue; 0 if not. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclpCheckStackSpace() +{ + return StackSpace() > TCL_MAC_STACK_THRESHOLD; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_Init -- + * + * This procedure is typically invoked by Tcl_AppInit procedures + * to perform additional initialization for a Tcl interpreter, + * such as sourcing the "init.tcl" script. + * + * Results: + * Returns a standard Tcl completion code and sets interp->result + * if there is an error. + * + * Side effects: + * Depends on what's in the init.tcl script. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_Init( + Tcl_Interp *interp) /* Interpreter to initialize. */ +{ + static char initCmd[] = + "if {[catch {source -rsrc Init}] != 0} {\n\ + if [file exists [info library]:init.tcl] {\n\ + source [info library]:init.tcl\n\ + } else {\n\ + set msg \"can't find Init resource or [info library]:init.tcl;\"\n\ + append msg \" perhaps you need to\\ninstall Tcl or set your \"\n\ + append msg \"TCL_LIBRARY environment variable?\"\n\ + error $msg\n\ + }\n}\n\ + if {[catch {source -rsrc History}] != 0} {\n\ + if [file exists [info library]:history.tcl] {\n\ + source [info library]:history.tcl\n\ + } else {\n\ + set msg \"can't find History resource or [info library]:history.tcl;\"\n\ + append msg \" perhaps you need to\\ninstall Tcl or set your \"\n\ + append msg \"TCL_LIBRARY environment variable?\"\n\ + error $msg\n\ + }\n}\n\ + if {[catch {source -rsrc Word}] != 0} {\n\ + if [file exists [info library]:word.tcl] {\n\ + source [info library]:word.tcl\n\ + } else {\n\ + set msg \"can't find Word resource or [info library]:word.tcl;\"\n\ + append msg \" perhaps you need to\\ninstall Tcl or set your \"\n\ + append msg \"TCL_LIBRARY environment variable?\"\n\ + error $msg\n\ + }\n}"; + + /* + * For Macintosh applications the Init function may be contained in + * the application resources. If it exists we use it - otherwise we + * look in the tcl_library directory. Ditto for the history command. + */ + + return Tcl_Eval(interp, initCmd); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_SourceRCFile -- + * + * This procedure is typically invoked by Tcl_Main or Tk_Main + * procedure to source an application specific rc file into the + * interpreter at startup time. This will either source a file + * in the "tcl_rcFileName" variable or a TEXT resource in the + * "tcl_rcRsrcName" variable. + * + * Results: + * None. + * + * Side effects: + * Depends on what's in the rc script. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_SourceRCFile( + Tcl_Interp *interp) /* Interpreter to source rc file into. */ +{ + Tcl_DString temp; + char *fileName; + Tcl_Channel errChannel; + Handle h; + + fileName = Tcl_GetVar(interp, "tcl_rcFileName", TCL_GLOBAL_ONLY); + + if (fileName != NULL) { + Tcl_Channel c; + char *fullName; + + Tcl_DStringInit(&temp); + fullName = Tcl_TranslateFileName(interp, fileName, &temp); + if (fullName == NULL) { + /* + * Couldn't translate the file name (e.g. it referred to a + * bogus user or there was no HOME environment variable). + * Just do nothing. + */ + } else { + + /* + * Test for the existence of the rc file before trying to read it. + */ + + c = Tcl_OpenFileChannel(NULL, fullName, "r", 0); + if (c != (Tcl_Channel) NULL) { + Tcl_Close(NULL, c); + if (Tcl_EvalFile(interp, fullName) != TCL_OK) { + errChannel = Tcl_GetStdChannel(TCL_STDERR); + if (errChannel) { + Tcl_Write(errChannel, interp->result, -1); + Tcl_Write(errChannel, "\n", 1); + } + } + } + } + Tcl_DStringFree(&temp); + } + + fileName = Tcl_GetVar(interp, "tcl_rcRsrcName", TCL_GLOBAL_ONLY); + + if (fileName != NULL) { + c2pstr(fileName); + h = GetNamedResource('TEXT', (StringPtr) fileName); + p2cstr((StringPtr) fileName); + if (h != NULL) { + if (Tcl_MacEvalResource(interp, fileName, 0, NULL) != TCL_OK) { + errChannel = Tcl_GetStdChannel(TCL_STDERR); + if (errChannel) { + Tcl_Write(errChannel, interp->result, -1); + Tcl_Write(errChannel, "\n", 1); + } + } + Tcl_ResetResult(interp); + ReleaseResource(h); + } + } +} diff --git a/tcl/mac/tclMacInt.h b/tcl/mac/tclMacInt.h new file mode 100644 index 00000000000..303b1d93060 --- /dev/null +++ b/tcl/mac/tclMacInt.h @@ -0,0 +1,79 @@ +/* + * tclMacInt.h -- + * + * Declarations of Macintosh specific shared variables and procedures. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#ifndef _TCLMACINT +#define _TCLMACINT + +#ifndef _TCL +# include "tcl.h" +#endif +#ifndef _TCLMAC +# include "tclMac.h" +#endif + +#include <Events.h> +#include <Files.h> + +#pragma export on + +/* + * Defines to control stack behavior + */ + +#define TCL_MAC_68K_STACK_GROWTH (256*1024) +#define TCL_MAC_STACK_THRESHOLD 16384 + +/* + * This flag is passed to TclMacRegisterResourceFork + * by a file (usually a library) whose resource fork + * should not be closed by the resource command. + */ + +#define TCL_RESOURCE_DONT_CLOSE 2 + +/* + * Typedefs used by Macintosh parts of Tcl. + */ +typedef pascal void (*ExitToShellProcPtr)(void); + +/* + * Prototypes for functions found in the tclMacUtil.c compatability library. + */ + +EXTERN int FSpGetDefaultDir _ANSI_ARGS_((FSSpecPtr theSpec)); +EXTERN int FSpSetDefaultDir _ANSI_ARGS_((FSSpecPtr theSpec)); +EXTERN OSErr FSpFindFolder _ANSI_ARGS_((short vRefNum, OSType folderType, + Boolean createFolder, FSSpec *spec)); +EXTERN void GetGlobalMouse _ANSI_ARGS_((Point *mouse)); + +/* + * Prototypes of Mac only internal functions. + */ + +EXTERN void TclCreateMacEventSource _ANSI_ARGS_((void)); +EXTERN int TclMacConsoleInit _ANSI_ARGS_((void)); +EXTERN void TclMacExitHandler _ANSI_ARGS_((void)); +EXTERN void TclMacInitExitToShell _ANSI_ARGS_((int usePatch)); +EXTERN OSErr TclMacInstallExitToShellPatch _ANSI_ARGS_(( + ExitToShellProcPtr newProc)); +EXTERN int TclMacOSErrorToPosixError _ANSI_ARGS_((int error)); +EXTERN void TclMacRemoveTimer _ANSI_ARGS_((void *timerToken)); +EXTERN void * TclMacStartTimer _ANSI_ARGS_((long ms)); +EXTERN int TclMacTimerExpired _ANSI_ARGS_((void *timerToken)); +EXTERN int TclMacRegisterResourceFork _ANSI_ARGS_((short fileRef, Tcl_Obj *tokenPtr, + int insert)); +EXTERN short TclMacUnRegisterResourceFork _ANSI_ARGS_((char *tokenPtr, Tcl_Obj *resultPtr)); + +#pragma export reset + +#endif /* _TCLMACINT */ diff --git a/tcl/mac/tclMacInterupt.c b/tcl/mac/tclMacInterupt.c new file mode 100644 index 00000000000..d0ea2865cf5 --- /dev/null +++ b/tcl/mac/tclMacInterupt.c @@ -0,0 +1,289 @@ +/* + * tclMacInterupt.c -- + * + * This file contains routines that deal with the Macintosh's low level + * time manager. This code provides a better resolution timer than what + * can be provided by WaitNextEvent. + * + * Copyright (c) 1996 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tclInt.h" +#include "tclMacInt.h" +#include <LowMem.h> +#include <Processes.h> +#include <Timer.h> + +/* + * Data structure for timer tasks. + */ +typedef struct TMInfo { + TMTask tmTask; + ProcessSerialNumber psn; + Point lastPoint; + Point newPoint; + long currentA5; + long ourA5; + int installed; +} TMInfo; + +/* + * Globals used within this file. + */ + +static TimerUPP sleepTimerProc = NULL; +static int interuptsInited = false; +static ProcessSerialNumber applicationPSN; +#define MAX_TIMER_ARRAY_SIZE 16 +static TMInfo timerInfoArray[MAX_TIMER_ARRAY_SIZE]; +static int topTimerElement = 0; + +/* + * Prototypes for procedures that are referenced only in this file: + */ + +#if !GENERATINGCFM +static TMInfo * GetTMInfo(void) ONEWORDINLINE(0x2E89); /* MOVE.L A1,(SP) */ +#endif +static void SleepTimerProc _ANSI_ARGS_((void)); +static pascal void CleanUpExitProc _ANSI_ARGS_((void)); +static void InitInteruptSystem _ANSI_ARGS_((void)); + +/* + *---------------------------------------------------------------------- + * + * InitInteruptSystem -- + * + * Does various initialization for the functions used in this + * file. Sets up Universial Pricedure Pointers, installs a trap + * patch for ExitToShell, etc. + * + * Results: + * None. + * + * Side effects: + * Various initialization. + * + *---------------------------------------------------------------------- + */ + +void +InitInteruptSystem() +{ + int i; + + sleepTimerProc = NewTimerProc(SleepTimerProc); + GetCurrentProcess(&applicationPSN); + for (i = 0; i < MAX_TIMER_ARRAY_SIZE; i++) { + timerInfoArray[i].installed = false; + } + + /* + * Install the ExitToShell patch. We use this patch instead + * of the Tcl exit mechanism because we need to ensure that + * these routines are cleaned up even if we crash or are forced + * to quit. There are some circumstances when the Tcl exit + * handlers may not fire. + */ + + TclMacInstallExitToShellPatch(CleanUpExitProc); + interuptsInited = true; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacStartTimer -- + * + * Install a Time Manager task to wake our process up in the + * future. The process should get a NULL event after ms + * milliseconds. + * + * Results: + * None. + * + * Side effects: + * Schedules our process to wake up. + * + *---------------------------------------------------------------------- + */ + +void * +TclMacStartTimer( + long ms) /* Milliseconds. */ +{ + TMInfo *timerInfoPtr; + + if (!interuptsInited) { + InitInteruptSystem(); + } + + /* + * Obtain a pointer for the timer. We only allocate up + * to MAX_TIMER_ARRAY_SIZE timers. If we are past that + * max we return NULL. + */ + if (topTimerElement < MAX_TIMER_ARRAY_SIZE) { + timerInfoPtr = &timerInfoArray[topTimerElement]; + topTimerElement++; + } else { + return NULL; + } + + /* + * Install timer to wake process in ms milliseconds. + */ + timerInfoPtr->tmTask.tmAddr = sleepTimerProc; + timerInfoPtr->tmTask.tmWakeUp = 0; + timerInfoPtr->tmTask.tmReserved = 0; + timerInfoPtr->psn = applicationPSN; + timerInfoPtr->installed = true; + + InsTime((QElemPtr) timerInfoPtr); + PrimeTime((QElemPtr) timerInfoPtr, (long) ms); + + return (void *) timerInfoPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacRemoveTimer -- + * + * Remove the timer event from the Time Manager. + * + * Results: + * None. + * + * Side effects: + * A scheduled timer would be removed. + * + *---------------------------------------------------------------------- + */ + +void +TclMacRemoveTimer( + void * timerToken) /* Token got from start timer. */ +{ + TMInfo *timerInfoPtr = (TMInfo *) timerToken; + + if (timerInfoPtr == NULL) { + return; + } + + RmvTime((QElemPtr) timerInfoPtr); + timerInfoPtr->installed = false; + topTimerElement--; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacTimerExpired -- + * + * Check to see if the installed timer has expired. + * + * Results: + * True if timer has expired, false otherwise. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclMacTimerExpired( + void * timerToken) /* Our token again. */ +{ + TMInfo *timerInfoPtr = (TMInfo *) timerToken; + + if ((timerInfoPtr == NULL) || + !(timerInfoPtr->tmTask.qType & kTMTaskActive)) { + return true; + } else { + return false; + } +} + +/* + *---------------------------------------------------------------------- + * + * SleepTimerProc -- + * + * Time proc is called by the is a callback routine placed in the + * system by Tcl_Sleep. The routine is called at interupt time + * and threrfor can not move or allocate memory. This call will + * schedule our process to wake up the next time the process gets + * around to consider running it. + * + * Results: + * None. + * + * Side effects: + * Schedules our process to wake up. + * + *---------------------------------------------------------------------- + */ + +static void +SleepTimerProc() +{ + /* + * In CFM code we can access our code directly. In 68k code that + * isn't based on CFM we must do a glorious hack. The function + * GetTMInfo is an inline assembler call that moves the pointer + * at A1 to the top of the stack. The Time Manager keeps the TMTask + * info record there before calling this call back. In order for + * this to work the infoPtr argument must be the *last* item on the + * stack. If we "piggyback" our data to the TMTask info record we + * can get access to the information we need. While this is really + * ugly - it's the way Apple recomends it be done - go figure... + */ + +#if GENERATINGCFM + WakeUpProcess(&applicationPSN); +#else + TMInfo * infoPtr; + + infoPtr = GetTMInfo(); + WakeUpProcess(&infoPtr->psn); +#endif +} + +/* + *---------------------------------------------------------------------- + * + * CleanUpExitProc -- + * + * This procedure is invoked as an exit handler when ExitToShell + * is called. It removes the system level timer handler if it + * is installed. This must be called or the Mac OS will more than + * likely crash. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static pascal void +CleanUpExitProc() +{ + int i; + + for (i = 0; i < MAX_TIMER_ARRAY_SIZE; i++) { + if (timerInfoArray[i].installed) { + RmvTime((QElemPtr) &timerInfoArray[i]); + timerInfoArray[i].installed = false; + } + } +} diff --git a/tcl/mac/tclMacLibrary.c b/tcl/mac/tclMacLibrary.c new file mode 100644 index 00000000000..e3668f5efe6 --- /dev/null +++ b/tcl/mac/tclMacLibrary.c @@ -0,0 +1,241 @@ +/* + * tclMacLibrary.c -- + * + * This file should be included in Tcl extensions that want to + * automatically oepn their resource forks when the code is linked. + * These routines should not be exported but should be compiled + * locally by each fragment. Many thanks to Jay Lieske + * <lieske@princeton.edu> who provide an initial version of this + * file. + * + * Copyright (c) 1996 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +/* + * Here is another place that we are using the old routine names... + */ + +#define OLDROUTINENAMES 1 + +#include <CodeFragments.h> +#include <Errors.h> +#include <Resources.h> +#include <Strings.h> +#include "tclMacInt.h" + +/* + * These function are not currently defined in any header file. The + * only place they should be used is in the Initialization and + * Termination entry points for a code fragment. The prototypes + * are included here to avoid compile errors. + */ + +OSErr TclMacInitializeFragment _ANSI_ARGS_(( + struct CFragInitBlock* initBlkPtr)); +void TclMacTerminateFragment _ANSI_ARGS_((void)); + +/* + * Static functions in this file. + */ + +static OSErr OpenLibraryResource _ANSI_ARGS_(( + struct CFragInitBlock* initBlkPtr)); +static void CloseLibraryResource _ANSI_ARGS_((void)); + +/* + * The refnum of the opened resource fork. + */ +static short ourResFile = kResFileNotOpened; + +/* + * This is the resource token for the our resource file. + * It stores the name we registered with the resource facility. + * We only need to use this if we are actually registering ourselves. + */ + +#ifdef TCL_REGISTER_LIBRARY +static Tcl_Obj *ourResToken; +#endif + +/* + *---------------------------------------------------------------------- + * + * TclMacInitializeFragment -- + * + * Called by MacOS CFM when the shared library is loaded. All this + * function really does is give Tcl a chance to open and register + * the resource fork of the library. + * + * Results: + * MacOS error code if loading should be canceled. + * + * Side effects: + * Opens the resource fork of the shared library file. + * + *---------------------------------------------------------------------- + */ + +OSErr +TclMacInitializeFragment( + struct CFragInitBlock* initBlkPtr) /* Pointer to our library. */ +{ + OSErr err = noErr; + +#ifdef __MWERKS__ + { + extern OSErr __initialize( CFragInitBlock* initBlkPtr); + err = __initialize((CFragInitBlock *) initBlkPtr); + } +#endif + if (err == noErr) + err = OpenLibraryResource( initBlkPtr); + return err; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacTerminateFragment -- + * + * Called by MacOS CFM when the shared library is unloaded. + * + * Results: + * None. + * + * Side effects: + * The resource fork of the code fragment is closed. + * + *---------------------------------------------------------------------- + */ + +void +TclMacTerminateFragment() +{ + CloseLibraryResource(); + +#ifdef __MWERKS__ + { + extern void __terminate(void); + __terminate(); + } +#endif +} + +/* + *---------------------------------------------------------------------- + * + * OpenLibraryResource -- + * + * This routine can be called by a MacOS fragment's initialiation + * function to open the resource fork of the file. + * Call it with the same data passed to the initialization function. + * If the fragment loading should fail if the resource fork can't + * be opened, then the initialization function can pass on this + * return value. + * + * If you #define TCL_REGISTER_RESOURCE before compiling this resource, + * then your library will register its open resource fork with the + * resource command. + * + * Results: + * It returns noErr on success and a MacOS error code on failure. + * + * Side effects: + * The resource fork of the code fragment is opened read-only and + * is installed at the head of the resource chain. + * + *---------------------------------------------------------------------- + */ + +static OSErr +OpenLibraryResource( + struct CFragInitBlock* initBlkPtr) +{ + /* + * The 3.0 version of the Universal headers changed CFragInitBlock + * to an opaque pointer type. CFragSystem7InitBlock is now the + * real pointer. + */ + +#if !defined(UNIVERSAL_INTERFACES_VERSION) || (UNIVERSAL_INTERFACES_VERSION < 0x0300) + struct CFragInitBlock *realInitBlkPtr = initBlkPtr; +#else + CFragSystem7InitBlock *realInitBlkPtr = (CFragSystem7InitBlock *) initBlkPtr; +#endif + FSSpec* fileSpec = NULL; + OSErr err = noErr; + + + if (realInitBlkPtr->fragLocator.where == kOnDiskFlat) { + fileSpec = realInitBlkPtr->fragLocator.u.onDisk.fileSpec; + } else if (realInitBlkPtr->fragLocator.where == kOnDiskSegmented) { + fileSpec = realInitBlkPtr->fragLocator.u.inSegs.fileSpec; + } else { + err = resFNotFound; + } + + /* + * Open the resource fork for this library in read-only mode. + * This will make it the current res file, ahead of the + * application's own resources. + */ + + if (fileSpec != NULL) { + ourResFile = FSpOpenResFile(fileSpec, fsRdPerm); + if (ourResFile == kResFileNotOpened) { + err = ResError(); + } else { +#ifdef TCL_REGISTER_LIBRARY + ourResToken = Tcl_NewObj(); + Tcl_IncrRefCount(ourResToken); + p2cstr(realInitBlkPtr->libName); + Tcl_SetStringObj(ourResToken, (char *) realInitBlkPtr->libName, -1); + c2pstr((char *) realInitBlkPtr->libName); + TclMacRegisterResourceFork(ourResFile, ourResToken, + TCL_RESOURCE_DONT_CLOSE); +#endif + SetResFileAttrs(ourResFile, mapReadOnly); + } + } + + return err; +} + +/* + *---------------------------------------------------------------------- + * + * CloseLibraryResource -- + * + * This routine should be called by a MacOS fragment's termination + * function to close the resource fork of the file + * that was opened with OpenLibraryResource. + * + * Results: + * None. + * + * Side effects: + * The resource fork of the code fragment is closed. + * + *---------------------------------------------------------------------- + */ + +static void +CloseLibraryResource() +{ + if (ourResFile != kResFileNotOpened) { +#ifdef TCL_REGISTER_LIBRARY + int length; + TclMacUnRegisterResourceFork( + Tcl_GetStringFromObj(ourResToken, &length), + NULL); + Tcl_DecrRefCount(ourResToken); +#endif + CloseResFile(ourResFile); + ourResFile = kResFileNotOpened; + } +} diff --git a/tcl/mac/tclMacLibrary.r b/tcl/mac/tclMacLibrary.r new file mode 100644 index 00000000000..8da1a8119e1 --- /dev/null +++ b/tcl/mac/tclMacLibrary.r @@ -0,0 +1,223 @@ +/* + * tclMacLibrary.r -- + * + * This file creates resources used by the Tcl shared library. + * Many thanks go to "Jay Lieske, Jr." <lieske@princeton.edu> who + * wrote the initial version of this file. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <Types.r> +#include <SysTypes.r> + +/* + * The folowing include and defines help construct + * the version string for Tcl. + */ + +#define RESOURCE_INCLUDED +#include "tcl.h" + +#if (TCL_RELEASE_LEVEL == 0) +# define RELEASE_LEVEL alpha +#elif (TCL_RELEASE_LEVEL == 1) +# define RELEASE_LEVEL beta +#elif (TCL_RELEASE_LEVEL == 2) +# define RELEASE_LEVEL final +#endif + +#if (TCL_RELEASE_LEVEL == 2) +# define MINOR_VERSION (TCL_MINOR_VERSION * 16) + TCL_RELEASE_SERIAL +#else +# define MINOR_VERSION TCL_MINOR_VERSION * 16 +#endif + +resource 'vers' (1) { + TCL_MAJOR_VERSION, MINOR_VERSION, + RELEASE_LEVEL, 0x00, verUS, + TCL_PATCH_LEVEL, + TCL_PATCH_LEVEL ", by Ray Johnson © Sun Microsystems" +}; + +resource 'vers' (2) { + TCL_MAJOR_VERSION, MINOR_VERSION, + RELEASE_LEVEL, 0x00, verUS, + TCL_PATCH_LEVEL, + "Tcl Library " TCL_PATCH_LEVEL " © 1996" +}; + +/* + * Currently the creator for all Tcl/Tk libraries and extensions + * should be 'TclL'. This will allow those extension and libraries + * to use the common icon for Tcl extensions. However, this signature + * still needs to be approved by the signature police at Apple and may + * change. + */ +#define TCL_CREATOR 'TclL' +#define TCL_LIBRARY_RESOURCES 2000 + +/* + * The 'BNDL' resource is the primary link between a file's + * creator/type and its icon. This resource acts for all Tcl shared + * libraries; other libraries will not need one and ought to use + * custom icons rather than new file types for a different appearance. + */ + +resource 'BNDL' (TCL_LIBRARY_RESOURCES, "Tcl bundle", purgeable) +{ + TCL_CREATOR, + 0, + { /* array TypeArray: 2 elements */ + /* [1] */ + 'FREF', + { /* array IDArray: 1 elements */ + /* [1] */ + 0, TCL_LIBRARY_RESOURCES + }, + /* [2] */ + 'ICN#', + { /* array IDArray: 1 elements */ + /* [1] */ + 0, TCL_LIBRARY_RESOURCES + } + } +}; + +resource 'FREF' (TCL_LIBRARY_RESOURCES, purgeable) +{ + 'shlb', 0, "" +}; + +type TCL_CREATOR as 'STR '; +resource TCL_CREATOR (0, purgeable) { + "Tcl Library " TCL_PATCH_LEVEL " © 1996" +}; + +/* + * The 'kind' resource works with a 'BNDL' in Macintosh Easy Open + * to affect the text the Finder displays in the "kind" column and + * file info dialog. This information will be applied to all files + * with the listed creator and type. + */ + +resource 'kind' (TCL_LIBRARY_RESOURCES, "Tcl kind", purgeable) { + TCL_CREATOR, + 0, /* region = USA */ + { + 'shlb', "Tcl Library" + } +}; + + +/* + * The -16397 string will be displayed by Finder when a user + * tries to open the shared library. The string should + * give the user a little detail about the library's capabilities + * and enough information to install the library in the correct location. + * A similar string should be placed in all shared libraries. + */ +resource 'STR ' (-16397, purgeable) { + "Tcl Library\n\n" + "This is the core library needed to run Tool Command Language programs. " + "To work properly, it should be placed in the ÔTool Command LanguageÕ folder " + "within the Extensions folder." +}; + +/* + * The mechanisim below loads Tcl source into the resource fork of the + * application. The example below creates a TEXT resource named + * "Init" from the file "init.tcl". This allows applications to use + * Tcl to define the behavior of the application without having to + * require some predetermined file structure - all needed Tcl "files" + * are located within the application. To source a file for the + * resource fork the source command has been modified to support + * sourcing from resources. In the below case "source -rsrc {Init}" + * will load the TEXT resource named "Init". + */ + +read 'TEXT' (TCL_LIBRARY_RESOURCES, "Init", purgeable) "::library:init.tcl"; +read 'TEXT' (TCL_LIBRARY_RESOURCES + 1, "History", purgeable) "::library:history.tcl"; +read 'TEXT' (TCL_LIBRARY_RESOURCES + 2, "Word", purgeable,preload) "::library:word.tcl"; + +/* + * The following are icons for the shared library. + */ + +data 'icl4' (2000, "Tcl Shared Library", purgeable) { + $"0FFF FFFF FFFF FFFF FFFF FFFF FFFF 0000" + $"F000 0000 0000 0000 0000 0000 000C F000" + $"F0CC CFFF CCCC CCC6 66CC CCCC CCCC F000" + $"F0CC CFFF FFFF FF66 F6CC CCCC CCCC F000" + $"F0CC CFFF 2000 0D66 6CCC CCCC CCCC F000" + $"F0CC CFFF 0202 056F 6E5C CCCC CCCC F000" + $"F0CC CFFF 2020 C666 F66F CCCC CCCC F000" + $"F0CC CFFF 0200 B66F 666B FCCC CCCC F000" + $"F0FC CFFF B020 55F6 6F52 BFCC CCCC F000" + $"FF0F 0CCC FB02 5665 66D0 2FCC CCCC F0F0" + $"F00F 0CCC CFB0 BF55 F6CF FFCC CCCC FFCF" + $"000F 0CCC CCFB 06C9 66CC CCCC CCCC F0CF" + $"000F 0CCC CCCF 56C6 6CCC CCCC CCCC CCCF" + $"000F 0CCC CCCC 6FC6 FCCC CCCC CCCC CCCF" + $"000F 0CCC CCCC 65C5 65CC CCCC CCCC CCCF" + $"000F 0CCC CCCC 55D6 57CC CCCC CCCC CCCF" + $"000F 0CCC CCCC 65CF 6CCC CCCC CCCC CCCF" + $"000F 0CCC CCCC 5AC6 6CFF CCCC CCCC CCCF" + $"000F 0CCC CCCC 65C5 6CF0 FCCC CCCC CCCF" + $"000F 0CCC CCCC CECF CCF0 0FCC CCCC CCCF" + $"000F 0CCC CCCC C5C6 CCCF 20FC CCCC FCCF" + $"F00F 0CCC CCCF FFD5 CCCC F20F CCCC FFCF" + $"FF0F 0CCC CCCF 20CF CCCC F020 FCCC F0F0" + $"F0F0 CCCC CCCF B2C2 FFFF 0002 0FFC F000" + $"F00C CCCC CCCC FBC0 2000 0020 2FFC F000" + $"F0CC CCCC CCCC CFCB 0202 0202 0FFC F000" + $"F0CC CCCC CCCC CCCF B020 2020 2FFC F000" + $"F0CC CCCC CCCC CCDC FBBB BBBB BFFC F000" + $"F0CC CCCC CCCC CCCC CFFF FFFF FFFC F000" + $"F0CC CCCC CCCC CCCC CCCC CCCC CFFC F000" + $"FCCC CCCC CCCC CCCC CCCC CCCC CCCC F000" + $"0FFF FFFF FFFF FFFF FFFF FFFF FFFF 0000" +}; + +data 'ICN#' (2000, "Tcl Shared Library", purgeable) { + $"7FFF FFF0 8000 0008 8701 C008 87FF C008" + $"8703 8008 8707 E008 8707 F008 870F F808" + $"A78F EC08 D0CF C40A 906F DC0D 1035 C009" + $"101D 8001 100D 8001 100D C001 100D C001" + $"100D 8001 100D B001 100D A801 1005 2401" + $"1005 1209 901D 090D D011 088A A018 F068" + $"800C 0068 8005 0068 8001 8068 8000 FFE8" + $"8000 7FE8 8000 0068 8000 0008 7FFF FFF0" + $"7FFF FFF0 FFFF FFF8 FFFF FFF8 FFFF FFF8" + $"FFFF FFF8 FFFF FFF8 FFFF FFF8 FFFF FFF8" + $"FFFF FFF8 DFFF FFFA 9FFF FFFF 1FFF FFFF" + $"1FFF FFFF 1FFF FFFF 1FFF FFFF 1FFF FFFF" + $"1FFF FFFF 1FFF FFFF 1FFF FFFF 1FFF FFFF" + $"1FFF FFFF 9FFF FFFF DFFF FFFA FFFF FFF8" + $"FFFF FFF8 FFFF FFF8 FFFF FFF8 FFFF FFF8" + $"FFFF FFF8 FFFF FFF8 FFFF FFF8 7FFF FFF0" +}; + +data 'ics#' (2000, "Tcl Shared Library", purgeable) { + $"FFFE B582 BB82 B3C2 BFA2 43C3 4381 4381" + $"4381 4763 4392 856E 838E 81AE 811E FFFE" + $"FFFE FFFE FFFE FFFE FFFE FFFF 7FFF 7FFF" + $"7FFF 7FFF 7FFF FFFE FFFE FFFE FFFE FFFE" +}; + +data 'ics4' (2000, "Tcl Shared Library", purgeable) { + $"FFFF FFFF FFFF FFF0 FCFF DED5 6CCC CCF0" + $"FCFF C0D6 ECCC CCF0 FCFF 2056 65DC CCF0" + $"FDFE D256 6DAC CCFF FFCC DDDE 5DDC CCEF" + $"0FCC CD67 5CCC CCCF 0FCC CC5D 6CCC CCCF" + $"0FCC CC5D 5CCC CCCF 0FCC CCD5 5CCC CCCF" + $"FFCC CFFD CCFF CCFF FCCC CF2D DF20 FCFC" + $"FCCC CCFD D202 FEF0 FCCC CC0D 2020 FEF0" + $"FCCC CCCD FBBB FEF0 FFFF FFFF FFFF FFE0" +}; + diff --git a/tcl/mac/tclMacLoad.c b/tcl/mac/tclMacLoad.c new file mode 100644 index 00000000000..551096b0137 --- /dev/null +++ b/tcl/mac/tclMacLoad.c @@ -0,0 +1,245 @@ +/* + * tclMacLoad.c -- + * + * This procedure provides a version of the TclLoadFile for use + * on the Macintosh. This procedure will only work with systems + * that use the Code Fragment Manager. + * + * Copyright (c) 1995-1996 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <CodeFragments.h> +#include <Errors.h> +#include <Resources.h> +#include <Strings.h> +#include <FSpCompat.h> + +/* + * Seems that the 3.0.1 Universal headers leave this define out. So we + * define it here... + */ + +#ifndef fragNoErr + #define fragNoErr noErr +#endif + +#include "tclPort.h" +#include "tclInt.h" +#include "tclMacInt.h" + +#if GENERATINGPOWERPC + #define OUR_ARCH_TYPE kPowerPCCFragArch +#else + #define OUR_ARCH_TYPE kMotorola68KCFragArch +#endif + +/* + * The following data structure defines the structure of a code fragment + * resource. We can cast the resource to be of this type to access + * any fields we need to see. + */ +struct CfrgHeader { + long res1; + long res2; + long version; + long res3; + long res4; + long filler1; + long filler2; + long itemCount; + char arrayStart; /* Array of externalItems begins here. */ +}; +typedef struct CfrgHeader CfrgHeader, *CfrgHeaderPtr, **CfrgHeaderPtrHand; + +/* + * The below structure defines a cfrag item within the cfrag resource. + */ +struct CfrgItem { + OSType archType; + long updateLevel; + long currVersion; + long oldDefVersion; + long appStackSize; + short appSubFolder; + char usage; + char location; + long codeOffset; + long codeLength; + long res1; + long res2; + short itemSize; + Str255 name; /* This is actually variable sized. */ +}; +typedef struct CfrgItem CfrgItem; + +/* + *---------------------------------------------------------------------- + * + * TclLoadFile -- + * + * This procedure is called to carry out dynamic loading of binary + * code for the Macintosh. This implementation is based on the + * Code Fragment Manager & will not work on other systems. + * + * Results: + * The result is TCL_ERROR, and an error message is left in + * interp->result. + * + * Side effects: + * New binary code is loaded. + * + *---------------------------------------------------------------------- + */ + +int +TclLoadFile( + Tcl_Interp *interp, /* Used for error reporting. */ + char *fileName, /* Name of the file containing the desired + * code. */ + char *sym1, char *sym2, /* Names of two procedures to look up in + * the file's symbol table. */ + Tcl_PackageInitProc **proc1Ptr, + Tcl_PackageInitProc **proc2Ptr) + /* Where to return the addresses corresponding + * to sym1 and sym2. */ +{ + CFragConnectionID connID; + Ptr dummy; + OSErr err; + CFragSymbolClass symClass; + FSSpec fileSpec; + short fragFileRef, saveFileRef; + Handle fragResource; + UInt32 offset = 0; + UInt32 length = kCFragGoesToEOF; + char packageName[255]; + Str255 errName; + + /* + * First thing we must do is infer the package name from the sym1 + * variable. This is kind of dumb since the caller actually knows + * this value, it just doesn't give it to us. + */ + strcpy(packageName, sym1); + *packageName = (char) tolower(*packageName); + packageName[strlen(packageName) - 5] = NULL; + + err = FSpLocationFromPath(strlen(fileName), fileName, &fileSpec); + if (err != noErr) { + interp->result = "could not locate shared library"; + return TCL_ERROR; + } + + /* + * See if this fragment has a 'cfrg' resource. It will tell us were + * to look for the fragment in the file. If it doesn't exist we will + * assume we have a ppc frag using the whole data fork. If it does + * exist we find the frag that matches the one we are looking for and + * get the offset and size from the resource. + */ + saveFileRef = CurResFile(); + SetResLoad(false); + fragFileRef = FSpOpenResFile(&fileSpec, fsRdPerm); + SetResLoad(true); + if (fragFileRef != -1) { + UseResFile(fragFileRef); + fragResource = Get1Resource(kCFragResourceType, kCFragResourceID); + HLock(fragResource); + if (ResError() == noErr) { + CfrgItem* srcItem; + long itemCount, index; + Ptr itemStart; + + itemCount = (*(CfrgHeaderPtrHand)fragResource)->itemCount; + itemStart = &(*(CfrgHeaderPtrHand)fragResource)->arrayStart; + for (index = 0; index < itemCount; + index++, itemStart += srcItem->itemSize) { + srcItem = (CfrgItem*)itemStart; + if (srcItem->archType != OUR_ARCH_TYPE) continue; + if (!strncasecmp(packageName, (char *) srcItem->name + 1, + srcItem->name[0])) { + offset = srcItem->codeOffset; + length = srcItem->codeLength; + } + } + } + /* + * Close the resource file. If the extension wants to reopen the + * resource fork it should use the tclMacLibrary.c file during it's + * construction. + */ + HUnlock(fragResource); + ReleaseResource(fragResource); + CloseResFile(fragFileRef); + UseResFile(saveFileRef); + } + + /* + * Now we can attempt to load the fragement using the offset & length + * obtained from the resource. We don't worry about the main entry point + * as we are going to search for specific entry points passed to us. + */ + + c2pstr(packageName); + err = GetDiskFragment(&fileSpec, offset, length, (StringPtr) packageName, + kLoadCFrag, &connID, &dummy, errName); + if (err != fragNoErr) { + p2cstr(errName); + Tcl_AppendResult(interp, "couldn't load file \"", fileName, + "\": ", errName, (char *) NULL); + return TCL_ERROR; + } + + c2pstr(sym1); + err = FindSymbol(connID, (StringPtr) sym1, (Ptr *) proc1Ptr, &symClass); + p2cstr((StringPtr) sym1); + if (err != fragNoErr || symClass == kDataCFragSymbol) { + interp->result = + "could not find Initialization routine in library"; + return TCL_ERROR; + } + + c2pstr(sym2); + err = FindSymbol(connID, (StringPtr) sym2, (Ptr *) proc2Ptr, &symClass); + p2cstr((StringPtr) sym2); + if (err != fragNoErr || symClass == kDataCFragSymbol) { + *proc2Ptr = NULL; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclGuessPackageName -- + * + * If the "load" command is invoked without providing a package + * name, this procedure is invoked to try to figure it out. + * + * Results: + * Always returns 0 to indicate that we couldn't figure out a + * package name; generic code will then try to guess the package + * from the file name. A return value of 1 would have meant that + * we figured out the package name and put it in bufPtr. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclGuessPackageName( + char *fileName, /* Name of file containing package (already + * translated to local form if needed). */ + Tcl_DString *bufPtr) /* Initialized empty dstring. Append + * package name to this if possible. */ +{ + return 0; +} diff --git a/tcl/mac/tclMacMSLPrefix.h b/tcl/mac/tclMacMSLPrefix.h new file mode 100644 index 00000000000..56af9b22c3a --- /dev/null +++ b/tcl/mac/tclMacMSLPrefix.h @@ -0,0 +1,24 @@ +/* + * tclMacMSLPrefix.h -- + * + * A wrapper for the MSL ansi_prefix.mac.h file. This just turns export on + * after including the MSL prefix file, so we can export symbols from the MSL + * and through the Tcl shared libraries + * + * + * Copyright (c) 1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <ansi_prefix.mac.h> +/* + * "export" is a MetroWerks specific pragma. It flags the linker that + * any symbols that are defined when this pragma is on will be exported + * to shared libraries that link with this library. + */ + +#pragma export on diff --git a/tcl/mac/tclMacMath.h b/tcl/mac/tclMacMath.h new file mode 100644 index 00000000000..cedefb7f078 --- /dev/null +++ b/tcl/mac/tclMacMath.h @@ -0,0 +1,145 @@ +/* + * tclMacMath.h -- + * + * This file is necessary because of Metrowerks CodeWarrior Pro 1 + * on the Macintosh. With 8-byte doubles turned on, the definitions of + * sin, cos, acos, etc., are screwed up. They are fine as long as + * they are used as function calls, but if the function pointers + * are passed around and used, they will crash hard on the 68K. + * + * Copyright (c) 1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#ifndef _TCLMACMATH +#define _TCLMACMATH + +#include <math.h> + +#if defined(__MWERKS__) && !defined(__POWERPC__) +#if __option(IEEEdoubles) + +# ifdef cos +# undef cos +# define cos cosd +# endif + +# ifdef sin +# undef sin +# define sin sind +# endif + +# ifdef tan +# undef tan +# define tan tand +# endif + +# ifdef acos +# undef acos +# define acos acosd +# endif + +# ifdef asin +# undef asin +# define asin asind +# endif + +# ifdef atan +# undef atan +# define atan atand +# endif + +# ifdef cosh +# undef cosh +# define cosh coshd +# endif + +# ifdef sinh +# undef sinh +# define sinh sinhd +# endif + +# ifdef tanh +# undef tanh +# define tanh tanhd +# endif + +# ifdef exp +# undef exp +# define exp expd +# endif + +# ifdef ldexp +# undef ldexp +# define ldexp ldexpd +# endif + +# ifdef log +# undef log +# define log logd +# endif + +# ifdef log10 +# undef log10 +# define log10 log10d +# endif + +# ifdef fabs +# undef fabs +# define fabs fabsd +# endif + +# ifdef sqrt +# undef sqrt +# define sqrt sqrtd +# endif + +# ifdef fmod +# undef fmod +# define fmod fmodd +# endif + +# ifdef atan2 +# undef atan2 +# define atan2 atan2d +# endif + +# ifdef frexp +# undef frexp +# define frexp frexpd +# endif + +# ifdef modf +# undef modf +# define modf modfd +# endif + +# ifdef pow +# undef pow +# define pow powd +# endif + +# ifdef ceil +# undef ceil +# define ceil ceild +# endif + +# ifdef floor +# undef floor +# define floor floord +# endif +#endif +#endif + +#if (defined(THINK_C) || defined(__MWERKS__)) +#pragma export on +double hypotd(double x, double y); +#define hypot hypotd +#pragma export reset +#endif + +#endif /* _TCLMACMATH */ diff --git a/tcl/mac/tclMacNotify.c b/tcl/mac/tclMacNotify.c new file mode 100644 index 00000000000..db221d72d11 --- /dev/null +++ b/tcl/mac/tclMacNotify.c @@ -0,0 +1,416 @@ +/* + * tclMacNotify.c -- + * + * This file contains Macintosh-specific procedures for the notifier, + * which is the lowest-level part of the Tcl event loop. This file + * works together with ../generic/tclNotify.c. + * + * Copyright (c) 1995-1996 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tclInt.h" +#include "tclPort.h" +#include "tclMac.h" +#include "tclMacInt.h" +#include <signal.h> +#include <Events.h> +#include <LowMem.h> +#include <Processes.h> +#include <Timer.h> + + +/* + * This is necessary to work around a bug in Apple's Universal header files + * for the CFM68K libraries. + */ + +#ifdef __CFM68K__ +#undef GetEventQueue +extern pascal QHdrPtr GetEventQueue(void) + THREEWORDINLINE(0x2EBC, 0x0000, 0x014A); +#pragma import list GetEventQueue +#define GetEvQHdr() GetEventQueue() +#endif + +/* + * The follwing static indicates whether this module has been initialized. + */ + +static int initialized = 0; + +/* + * The following structure contains the state information for the + * notifier module. + */ + +static struct { + int timerActive; /* 1 if timer is running. */ + Tcl_Time timer; /* Time when next timer event is expected. */ + int flags; /* OR'ed set of flags defined below. */ + Point lastMousePosition; /* Last known mouse location. */ + RgnHandle utilityRgn; /* Region used as the mouse region for + * WaitNextEvent and the update region when + * checking for events. */ + Tcl_MacConvertEventPtr eventProcPtr; + /* This pointer holds the address of the + * function that will handle all incoming + * Macintosh events. */ +} notifier; + +/* + * The following defines are used in the flags field of the notifier struct. + */ + +#define NOTIFY_IDLE (1<<1) /* Tcl_ServiceIdle should be called. */ +#define NOTIFY_TIMER (1<<2) /* Tcl_ServiceTimer should be called. */ + +/* + * Prototypes for procedures that are referenced only in this file: + */ + +static int HandleMacEvents _ANSI_ARGS_((void)); +static void InitNotifier _ANSI_ARGS_((void)); +static void NotifierExitHandler _ANSI_ARGS_(( + ClientData clientData)); + +/* + *---------------------------------------------------------------------- + * + * InitNotifier -- + * + * Initializes the notifier structure. + * + * Results: + * None. + * + * Side effects: + * Creates a new exit handler. + * + *---------------------------------------------------------------------- + */ + +static void +InitNotifier(void) +{ + initialized = 1; + memset(¬ifier, 0, sizeof(notifier)); + Tcl_CreateExitHandler(NotifierExitHandler, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * NotifierExitHandler -- + * + * This function is called to cleanup the notifier state before + * Tcl is unloaded. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +NotifierExitHandler( + ClientData clientData) /* Not used. */ +{ + initialized = 0; +} + +/* + *---------------------------------------------------------------------- + * + * HandleMacEvents -- + * + * This function checks for events from the Macintosh event queue. + * + * Results: + * Returns 1 if event found, 0 otherwise. + * + * Side effects: + * Pulls events off of the Mac event queue and then calls + * convertEventProc. + * + *---------------------------------------------------------------------- + */ + +static int +HandleMacEvents(void) +{ + EventRecord theEvent; + int eventFound = 0, needsUpdate = 0; + Point currentMouse; + WindowRef windowRef; + Rect mouseRect; + + /* + * Check for mouse moved events. These events aren't placed on the + * system event queue unless we call WaitNextEvent. + */ + + GetGlobalMouse(¤tMouse); + if ((notifier.eventProcPtr != NULL) && + !EqualPt(currentMouse, notifier.lastMousePosition)) { + notifier.lastMousePosition = currentMouse; + theEvent.what = nullEvent; + if ((*notifier.eventProcPtr)(&theEvent) == true) { + eventFound = 1; + } + } + + /* + * Check for update events. Since update events aren't generated + * until we call GetNextEvent, we may need to force a call to + * GetNextEvent, even if the queue is empty. + */ + + for (windowRef = FrontWindow(); windowRef != NULL; + windowRef = GetNextWindow(windowRef)) { + GetWindowUpdateRgn(windowRef, notifier.utilityRgn); + if (!EmptyRgn(notifier.utilityRgn)) { + needsUpdate = 1; + break; + } + } + + /* + * Process events from the OS event queue. + */ + + while (needsUpdate || (GetEvQHdr()->qHead != NULL)) { + GetGlobalMouse(¤tMouse); + SetRect(&mouseRect, currentMouse.h, currentMouse.v, + currentMouse.h + 1, currentMouse.v + 1); + RectRgn(notifier.utilityRgn, &mouseRect); + + WaitNextEvent(everyEvent, &theEvent, 5, notifier.utilityRgn); + needsUpdate = 0; + if ((notifier.eventProcPtr != NULL) + && ((*notifier.eventProcPtr)(&theEvent) == true)) { + eventFound = 1; + } + } + + return eventFound; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_SetTimer -- + * + * This procedure sets the current notifier timer value. The + * notifier will ensure that Tcl_ServiceAll() is called after + * the specified interval, even if no events have occurred. + * + * Results: + * None. + * + * Side effects: + * Replaces any previous timer. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_SetTimer( + Tcl_Time *timePtr) /* New value for interval timer. */ +{ + if (!timePtr) { + notifier.timerActive = 0; + } else { + /* + * Compute when the timer should fire. + */ + + TclpGetTime(¬ifier.timer); + notifier.timer.sec += timePtr->sec; + notifier.timer.usec += timePtr->usec; + if (notifier.timer.usec >= 1000000) { + notifier.timer.usec -= 1000000; + notifier.timer.sec += 1; + } + notifier.timerActive = 1; + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_WaitForEvent -- + * + * This function is called by Tcl_DoOneEvent to wait for new + * events on the message queue. If the block time is 0, then + * Tcl_WaitForEvent just polls the event queue without blocking. + * + * Results: + * Always returns 0. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_WaitForEvent( + Tcl_Time *timePtr) /* Maximum block time. */ +{ + int found; + EventRecord macEvent; + long sleepTime = 5; + long ms; + Point currentMouse; + void * timerToken; + Rect mouseRect; + + /* + * Compute the next timeout value. + */ + + if (!timePtr) { + ms = INT_MAX; + } else { + ms = (timePtr->sec * 1000) + (timePtr->usec / 1000); + } + timerToken = TclMacStartTimer((long) ms); + + /* + * Poll the Mac event sources. This loop repeats until something + * happens: a timeout, a socket event, mouse motion, or some other + * window event. Note that we don't call WaitNextEvent if another + * event is found to avoid context switches. This effectively gives + * events coming in via WaitNextEvent a slightly lower priority. + */ + + found = 0; + if (notifier.utilityRgn == NULL) { + notifier.utilityRgn = NewRgn(); + } + + while (!found) { + /* + * Check for generated and queued events. + */ + + if (HandleMacEvents()) { + found = 1; + } + + /* + * Check for time out. + */ + + if (!found && TclMacTimerExpired(timerToken)) { + found = 1; + } + + /* + * Check for window events. We may receive a NULL event for + * various reasons. 1) the timer has expired, 2) a mouse moved + * event is occuring or 3) the os is giving us time for idle + * events. Note that we aren't sharing the processor very + * well here. We really ought to do a better job of calling + * WaitNextEvent for time slicing purposes. + */ + + if (!found) { + /* + * Set up mouse region so we will wake if the mouse is moved. + * We do this by defining the smallest possible region around + * the current mouse position. + */ + + GetGlobalMouse(¤tMouse); + SetRect(&mouseRect, currentMouse.h, currentMouse.v, + currentMouse.h + 1, currentMouse.v + 1); + RectRgn(notifier.utilityRgn, &mouseRect); + + WaitNextEvent(everyEvent, &macEvent, sleepTime, + notifier.utilityRgn); + + if (notifier.eventProcPtr != NULL) { + if ((*notifier.eventProcPtr)(&macEvent) == true) { + found = 1; + } + } + } + } + TclMacRemoveTimer(timerToken); + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_Sleep -- + * + * Delay execution for the specified number of milliseconds. This + * is not a very good call to make. It will block the system - + * you will not even be able to switch applications. + * + * Results: + * None. + * + * Side effects: + * Time passes. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_Sleep( + int ms) /* Number of milliseconds to sleep. */ +{ + EventRecord dummy; + void *timerToken; + + if (ms <= 0) { + return; + } + + timerToken = TclMacStartTimer((long) ms); + while (1) { + WaitNextEvent(0, &dummy, (ms / 16.66) + 1, NULL); + + if (TclMacTimerExpired(timerToken)) { + break; + } + } + TclMacRemoveTimer(timerToken); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_MacSetEventProc -- + * + * This function sets the event handling procedure for the + * application. This function will be passed all incoming Mac + * events. This function usually controls the console or some + * other entity like Tk. + * + * Results: + * None. + * + * Side effects: + * Changes the event handling function. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_MacSetEventProc( + Tcl_MacConvertEventPtr procPtr) +{ + notifier.eventProcPtr = procPtr; +} diff --git a/tcl/mac/tclMacOSA.c b/tcl/mac/tclMacOSA.c new file mode 100644 index 00000000000..169d578bcd3 --- /dev/null +++ b/tcl/mac/tclMacOSA.c @@ -0,0 +1,2937 @@ +/* + * tclMacOSA.c -- + * + * This contains the initialization routines, and the implementation of + * the OSA and Component commands. These commands allow you to connect + * with the AppleScript or any other OSA component to compile and execute + * scripts. + * + * Copyright (c) 1996 Lucent Technologies and Jim Ingham + * Copyright (c) 1997 Sun Microsystems, Inc. + * + * See the file "License Terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#define MAC_TCL + +#include <Aliases.h> +#include <string.h> +#include <AppleEvents.h> +#include <AppleScript.h> +#include <OSA.h> +#include <OSAGeneric.h> +#include <Script.h> + +#include <FullPath.h> +#include <components.h> + +#include <resources.h> +#include <FSpCompat.h> +/* + * The following two Includes are from the More Files package. + */ +#include <MoreFiles.h> +#include <FullPath.h> + +#include "tcl.h" +#include "tclInt.h" + +/* + * I need this only for the call to FspGetFullPath, + * I'm really not poking my nose where it does not belong! + */ +#include "tclMacInt.h" + +/* + * Data structures used by the OSA code. + */ +typedef struct tclOSAScript { + OSAID scriptID; + OSType languageID; + long modeFlags; +} tclOSAScript; + +typedef struct tclOSAContext { + OSAID contextID; +} tclOSAContext; + +typedef struct tclOSAComponent { + char *theName; + ComponentInstance theComponent; /* The OSA Component represented */ + long componentFlags; + OSType languageID; + char *languageName; + Tcl_HashTable contextTable; /* Hash Table linking the context names & ID's */ + Tcl_HashTable scriptTable; + Tcl_Interp *theInterp; + OSAActiveUPP defActiveProc; + long defRefCon; +} tclOSAComponent; + +/* + * Prototypes for static procedures. + */ + +static pascal OSErr TclOSAActiveProc _ANSI_ARGS_((long refCon)); +static int TclOSACompileCmd _ANSI_ARGS_((Tcl_Interp *interp, + tclOSAComponent *OSAComponent, int argc, + char **argv)); +static int tclOSADecompileCmd _ANSI_ARGS_((Tcl_Interp * Interp, + tclOSAComponent *OSAComponent, int argc, + char **argv)); +static int tclOSADeleteCmd _ANSI_ARGS_((Tcl_Interp *interp, + tclOSAComponent *OSAComponent, int argc, + char **argv)); +static int tclOSAExecuteCmd _ANSI_ARGS_((Tcl_Interp *interp, + tclOSAComponent *OSAComponent, int argc, + char **argv)); +static int tclOSAInfoCmd _ANSI_ARGS_((Tcl_Interp *interp, + tclOSAComponent *OSAComponent, int argc, + char **argv)); +static int tclOSALoadCmd _ANSI_ARGS_((Tcl_Interp *interp, + tclOSAComponent *OSAComponent, int argc, + char **argv)); +static int tclOSARunCmd _ANSI_ARGS_((Tcl_Interp *interp, + tclOSAComponent *OSAComponent, int argc, + char **argv)); +static int tclOSAStoreCmd _ANSI_ARGS_((Tcl_Interp *interp, + tclOSAComponent *OSAComponent, int argc, char + **argv)); +static void GetRawDataFromDescriptor _ANSI_ARGS_((AEDesc *theDesc, + Ptr destPtr, Size destMaxSize, Size *actSize)); +static OSErr GetCStringFromDescriptor _ANSI_ARGS_(( + AEDesc *sourceDesc, char *resultStr, + Size resultMaxSize,Size *resultSize)); +static int Tcl_OSAComponentCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int argc, char **argv)); +static void getSortedHashKeys _ANSI_ARGS_((Tcl_HashTable *theTable, + char *pattern, Tcl_DString *theResult)); +static int ASCIICompareProc _ANSI_ARGS_((const void *first, + const void *second)); +static int Tcl_OSACmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int argc, char **argv)); +static void tclOSAClose _ANSI_ARGS_((ClientData clientData)); +static void tclOSACloseAll _ANSI_ARGS_((ClientData clientData)); +static tclOSAComponent *tclOSAMakeNewComponent _ANSI_ARGS_((Tcl_Interp *interp, + char *cmdName, char *languageName, + OSType scriptSubtype, long componentFlags)); +static int prepareScriptData _ANSI_ARGS_((int argc, char **argv, + Tcl_DString *scrptData ,AEDesc *scrptDesc)); +static void tclOSAResultFromID _ANSI_ARGS_((Tcl_Interp *interp, + ComponentInstance theComponent, OSAID resultID)); +static void tclOSAASError _ANSI_ARGS_((Tcl_Interp * interp, + ComponentInstance theComponent, char *scriptSource)); +static int tclOSAGetContextID _ANSI_ARGS_((tclOSAComponent *theComponent, + char *contextName, OSAID *theContext)); +static void tclOSAAddContext _ANSI_ARGS_((tclOSAComponent *theComponent, + char *contextName, const OSAID theContext)); +static int tclOSAMakeContext _ANSI_ARGS_((tclOSAComponent *theComponent, + char *contextName, OSAID *theContext)); +static int tclOSADeleteContext _ANSI_ARGS_((tclOSAComponent *theComponent, + char *contextName)); +static int tclOSALoad _ANSI_ARGS_((Tcl_Interp *interp, + tclOSAComponent *theComponent, char *resourceName, + int resourceNumber, char *fileName,OSAID *resultID)); +static int tclOSAStore _ANSI_ARGS_((Tcl_Interp *interp, + tclOSAComponent *theComponent, char *resourceName, + int resourceNumber, char *fileName,char *scriptName)); +static int tclOSAAddScript _ANSI_ARGS_((tclOSAComponent *theComponent, + char *scriptName, long modeFlags, OSAID scriptID)); +static int tclOSAGetScriptID _ANSI_ARGS_((tclOSAComponent *theComponent, + char *scriptName, OSAID *scriptID)); +static tclOSAScript * tclOSAGetScript _ANSI_ARGS_((tclOSAComponent *theComponent, + char *scriptName)); +static int tclOSADeleteScript _ANSI_ARGS_((tclOSAComponent *theComponent, + char *scriptName,char *errMsg)); + +/* + * "export" is a MetroWerks specific pragma. It flags the linker that + * any symbols that are defined when this pragma is on will be exported + * to shared libraries that link with this library. + */ + + +#pragma export on +int Tclapplescript_Init( Tcl_Interp *interp ); +#pragma export reset + +/* + *---------------------------------------------------------------------- + * + * Tclapplescript_Init -- + * + * Initializes the the OSA command which opens connections to + * OSA components, creates the AppleScript command, which opens an + * instance of the AppleScript component,and constructs the table of + * available languages. + * + * Results: + * A standard Tcl result. + * + * Side Effects: + * Opens one connection to the AppleScript component, if + * available. Also builds up a table of available OSA languages, + * and creates the OSA command. + * + *---------------------------------------------------------------------- + */ + +int +Tclapplescript_Init( + Tcl_Interp *interp) /* Tcl interpreter. */ +{ + char *errMsg = NULL; + OSErr myErr = noErr; + Boolean gotAppleScript = false; + Boolean GotOneOSALanguage = false; + ComponentDescription compDescr = { + kOSAComponentType, + (OSType) 0, + (OSType) 0, + (long) 0, + (long) 0 + }, *foundComp; + Component curComponent = (Component) 0; + ComponentInstance curOpenComponent; + Tcl_HashTable *ComponentTable; + Tcl_HashTable *LanguagesTable; + Tcl_HashEntry *hashEntry; + int newPtr; + AEDesc componentName = { typeNull, NULL }; + char nameStr[32]; + Size nameLen; + long appleScriptFlags; + + /* + * Here We Will Get The Available Osa Languages, Since They Can Only Be + * Registered At Startup... If You Dynamically Load Components, This + * Will Fail, But This Is Not A Common Thing To Do. + */ + + LanguagesTable = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); + + if (LanguagesTable == NULL) { + panic("Memory Error Allocating Languages Hash Table"); + } + + Tcl_SetAssocData(interp, "OSAScript_LangTable", NULL, LanguagesTable); + Tcl_InitHashTable(LanguagesTable, TCL_STRING_KEYS); + + + while ((curComponent = FindNextComponent(curComponent, &compDescr)) != 0) { + int nbytes = sizeof(ComponentDescription); + foundComp = (ComponentDescription *) + ckalloc(sizeof(ComponentDescription)); + myErr = GetComponentInfo(curComponent, foundComp, NULL, NULL, NULL); + if (foundComp->componentSubType == + kOSAGenericScriptingComponentSubtype) { + /* Skip the generic component */ + ckfree((char *) foundComp); + } else { + GotOneOSALanguage = true; + + /* + * This is gross: looks like I have to open the component just + * to get its name!!! GetComponentInfo is supposed to return + * the name, but AppleScript always returns an empty string. + */ + + curOpenComponent = OpenComponent(curComponent); + if (curOpenComponent == NULL) { + Tcl_AppendResult(interp,"Error opening component", + (char *) NULL); + return TCL_ERROR; + } + + myErr = OSAScriptingComponentName(curOpenComponent,&componentName); + if (myErr == noErr) { + myErr = GetCStringFromDescriptor(&componentName, + nameStr, 31, &nameLen); + AEDisposeDesc(&componentName); + } + CloseComponent(curOpenComponent); + + if (myErr == noErr) { + hashEntry = Tcl_CreateHashEntry(LanguagesTable, + nameStr, &newPtr); + Tcl_SetHashValue(hashEntry, (ClientData) foundComp); + } else { + Tcl_AppendResult(interp,"Error getting componentName.", + (char *) NULL); + return TCL_ERROR; + } + + /* + * Make sure AppleScript is loaded, otherwise we will + * not bother to make the AppleScript command. + */ + if (foundComp->componentSubType == kAppleScriptSubtype) { + appleScriptFlags = foundComp->componentFlags; + gotAppleScript = true; + } + } + } + + /* + * Create the OSA command. + */ + + if (!GotOneOSALanguage) { + Tcl_AppendResult(interp,"Could not find any OSA languages", + (char *) NULL); + return TCL_ERROR; + } + + /* + * Create the Component Assoc Data & put it in the interpreter. + */ + + ComponentTable = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); + + if (ComponentTable == NULL) { + panic("Memory Error Allocating Hash Table"); + } + + Tcl_SetAssocData(interp, "OSAScript_CompTable", NULL, ComponentTable); + + Tcl_InitHashTable(ComponentTable, TCL_STRING_KEYS); + + /* + * The OSA command is not currently supported. + Tcl_CreateCommand(interp, "OSA", Tcl_OSACmd, (ClientData) NULL, + (Tcl_CmdDeleteProc *) NULL); + */ + + /* + * Open up one AppleScript component, with a default context + * and tie it to the AppleScript command. + * If the user just wants single-threaded AppleScript execution + * this should be enough. + * + */ + + if (gotAppleScript) { + if (tclOSAMakeNewComponent(interp, "AppleScript", + "AppleScript English", kAppleScriptSubtype, + appleScriptFlags) == NULL ) { + return TCL_ERROR; + } + } + + return Tcl_PkgProvide(interp, "OSAConnect", "1.0"); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_OSACmd -- + * + * This is the command that provides the interface to the OSA + * component manager. The subcommands are: close: close a component, + * info: get info on components open, and open: get a new connection + * with the Scripting Component + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Depends on the subcommand, see the user documentation + * for more details. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_OSACmd( + ClientData clientData, + Tcl_Interp *interp, + int argc, + char **argv) +{ + static unsigned short componentCmdIndex = 0; + char autoName[32]; + char c; + int length; + Tcl_HashTable *ComponentTable = NULL; + + + if (argc == 1) { + Tcl_AppendResult(interp, "Wrong # of arguments, should be \"", + argv[0], " option\"", (char *) NULL); + return TCL_ERROR; + } + + c = *argv[1]; + length = strlen(argv[1]); + + /* + * Query out the Component Table, since most of these commands use it... + */ + + ComponentTable = (Tcl_HashTable *) Tcl_GetAssocData(interp, + "OSAScript_CompTable", (Tcl_InterpDeleteProc **) NULL); + + if (ComponentTable == NULL) { + Tcl_AppendResult(interp, "Error, could not get the Component Table", + " from the Associated data.", (char *) NULL); + return TCL_ERROR; + } + + if (c == 'c' && strncmp(argv[1],"close",length) == 0) { + Tcl_HashEntry *hashEntry; + if (argc != 3) { + Tcl_AppendResult(interp, "Wrong # of arguments, should be \"", + argv[0], " ",argv[1], " componentName\"", + (char *) NULL); + return TCL_ERROR; + } + + if ((hashEntry = Tcl_FindHashEntry(ComponentTable,argv[2])) == NULL) { + Tcl_AppendResult(interp, "Component \"", argv[2], "\" not found", + (char *) NULL); + return TCL_ERROR; + } else { + Tcl_DeleteCommand(interp,argv[2]); + return TCL_OK; + } + } else if (c == 'o' && strncmp(argv[1],"open",length) == 0) { + /* + * Default language is AppleScript. + */ + OSType scriptSubtype = kAppleScriptSubtype; + char *languageName = "AppleScript English"; + char *errMsg = NULL; + ComponentDescription *theCD; + + argv += 2; + argc -= 2; + + while (argc > 0 ) { + if (*argv[0] == '-') { + c = *(argv[0] + 1); + if (c == 'l' && strcmp(argv[0] + 1, "language") == 0) { + if (argc == 1) { + Tcl_AppendResult(interp, + "Error - no language provided for the -language switch", + (char *) NULL); + return TCL_ERROR; + } else { + Tcl_HashEntry *hashEntry; + Tcl_HashSearch search; + Boolean gotIt = false; + Tcl_HashTable *LanguagesTable; + + /* + * Look up the language in the languages table + * Do a simple strstr match, so AppleScript + * will match "AppleScript English"... + */ + + LanguagesTable = Tcl_GetAssocData(interp, + "OSAScript_LangTable", + (Tcl_InterpDeleteProc **) NULL); + + for (hashEntry = + Tcl_FirstHashEntry(LanguagesTable, &search); + hashEntry != NULL; + hashEntry = Tcl_NextHashEntry(&search)) { + languageName = Tcl_GetHashKey(LanguagesTable, + hashEntry); + if (strstr(languageName,argv[1]) != NULL) { + theCD = (ComponentDescription *) + Tcl_GetHashValue(hashEntry); + gotIt = true; + break; + } + } + if (!gotIt) { + Tcl_AppendResult(interp, + "Error, could not find the language \"", + argv[1], + "\" in the list of known languages.", + (char *) NULL); + return TCL_ERROR; + } + } + } + argc -= 2; + argv += 2; + } else { + Tcl_AppendResult(interp, "Expected a flag, but got ", + argv[0], (char *) NULL); + return TCL_ERROR; + } + } + + sprintf(autoName, "OSAComponent%-d", componentCmdIndex++); + if (tclOSAMakeNewComponent(interp, autoName, languageName, + theCD->componentSubType, theCD->componentFlags) == NULL ) { + return TCL_ERROR; + } else { + Tcl_SetResult(interp,autoName,TCL_VOLATILE); + return TCL_OK; + } + + } else if (c == 'i' && strncmp(argv[1],"info",length) == 0) { + if (argc == 2) { + Tcl_AppendResult(interp, "Wrong # of arguments, should be \"", + argv[0], " ", argv[1], " what\"", + (char *) NULL); + return TCL_ERROR; + } + + c = *argv[2]; + length = strlen(argv[2]); + + if (c == 'c' && strncmp(argv[2], "components", length) == 0) { + Tcl_DString theResult; + + Tcl_DStringInit(&theResult); + + if (argc == 3) { + getSortedHashKeys(ComponentTable,(char *) NULL, &theResult); + } else if (argc == 4) { + getSortedHashKeys(ComponentTable, argv[3], &theResult); + } else { + Tcl_AppendResult(interp, "Error: wrong # of arguments", + ", should be \"", argv[0], " ", argv[1], " ", + argv[2], " ?pattern?\".", (char *) NULL); + return TCL_ERROR; + } + Tcl_DStringResult(interp, &theResult); + return TCL_OK; + } else if (c == 'l' && strncmp(argv[2],"languages",length) == 0) { + Tcl_DString theResult; + Tcl_HashTable *LanguagesTable; + + Tcl_DStringInit(&theResult); + LanguagesTable = Tcl_GetAssocData(interp, + "OSAScript_LangTable", (Tcl_InterpDeleteProc **) NULL); + + if (argc == 3) { + getSortedHashKeys(LanguagesTable, (char *) NULL, &theResult); + } else if (argc == 4) { + getSortedHashKeys(LanguagesTable, argv[3], &theResult); + } else { + Tcl_AppendResult(interp, "Error: wrong # of arguments", + ", should be \"", argv[0], " ", argv[1], " ", + argv[2], " ?pattern?\".", (char *) NULL); + return TCL_ERROR; + } + Tcl_DStringResult(interp,&theResult); + return TCL_OK; + } else { + Tcl_AppendResult(interp, "Unknown option: ", argv[2], + " for OSA info, should be one of", + " \"components\" or \"languages\"", + (char *) NULL); + return TCL_ERROR; + } + } else { + Tcl_AppendResult(interp, "Unknown option: ", argv[1], + ", should be one of \"open\", \"close\" or \"info\".", + (char *) NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_OSAComponentCmd -- + * + * This is the command that provides the interface with an OSA + * component. The sub commands are: + * - compile ? -context context? scriptData + * compiles the script data, returns the ScriptID + * - decompile ? -context context? scriptData + * decompiles the script data, source code + * - execute ?-context context? scriptData + * compiles and runs script data + * - info what: get component info + * - load ?-flags values? fileName + * loads & compiles script data from fileName + * - run scriptId ?options? + * executes the compiled script + * + * Results: + * A standard Tcl result + * + * Side Effects: + * Depends on the subcommand, see the user documentation + * for more details. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_OSAComponentCmd( + ClientData clientData, + Tcl_Interp *interp, + int argc, + char **argv) +{ + int length; + char c; + + tclOSAComponent *OSAComponent = (tclOSAComponent *) clientData; + + if (argc == 1) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " option ?arg ...?\"", + (char *) NULL); + return TCL_ERROR; + } + + c = *argv[1]; + length = strlen(argv[1]); + if (c == 'c' && strncmp(argv[1], "compile", length) == 0) { + return TclOSACompileCmd(interp, OSAComponent, argc, argv); + } else if (c == 'l' && strncmp(argv[1], "load", length) == 0) { + return tclOSALoadCmd(interp, OSAComponent, argc, argv); + } else if (c == 'e' && strncmp(argv[1], "execute", length) == 0) { + return tclOSAExecuteCmd(interp, OSAComponent, argc, argv); + } else if (c == 'i' && strncmp(argv[1], "info", length) == 0) { + return tclOSAInfoCmd(interp, OSAComponent, argc, argv); + } else if (c == 'd' && strncmp(argv[1], "decompile", length) == 0) { + return tclOSADecompileCmd(interp, OSAComponent, argc, argv); + } else if (c == 'd' && strncmp(argv[1], "delete", length) == 0) { + return tclOSADeleteCmd(interp, OSAComponent, argc, argv); + } else if (c == 'r' && strncmp(argv[1], "run", length) == 0) { + return tclOSARunCmd(interp, OSAComponent, argc, argv); + } else if (c == 's' && strncmp(argv[1], "store", length) == 0) { + return tclOSAStoreCmd(interp, OSAComponent, argc, argv); + } else { + Tcl_AppendResult(interp,"bad option \"", argv[1], + "\": should be compile, decompile, delete, ", + "execute, info, load, run or store", + (char *) NULL); + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclOSACompileCmd -- + * + * This is the compile subcommand for the component command. + * + * Results: + * A standard Tcl result + * + * Side Effects: + * Compiles the script data either into a script or a script + * context. Adds the script to the component's script or context + * table. Sets interp's result to the name of the new script or + * context. + * + *---------------------------------------------------------------------- + */ + +static int +TclOSACompileCmd( + Tcl_Interp *interp, + tclOSAComponent *OSAComponent, + int argc, + char **argv) +{ + int tclError = TCL_OK; + int augment = 1; + int makeContext = 0; + char c; + char autoName[16]; + char buffer[32]; + char *resultName; + Boolean makeNewContext = false; + Tcl_DString scrptData; + AEDesc scrptDesc = { typeNull, NULL }; + long modeFlags = kOSAModeCanInteract; + OSAID resultID = kOSANullScript; + OSAID contextID = kOSANullScript; + OSAID parentID = kOSANullScript; + OSAError osaErr = noErr; + + if (!(OSAComponent->componentFlags && kOSASupportsCompiling)) { + Tcl_AppendResult(interp, + "OSA component does not support compiling", + (char *) NULL); + return TCL_ERROR; + } + + /* + * This signals that we should make up a name, which is the + * default behavior: + */ + + autoName[0] = '\0'; + resultName = NULL; + + if (argc == 2) { + numArgs: + Tcl_AppendResult(interp, + "wrong # args: should be \"", argv[0], " ", argv[1], + " ?options? code\"",(char *) NULL); + return TCL_ERROR; + } + + argv += 2; + argc -= 2; + + /* + * Do the argument parsing. + */ + + while (argc > 0) { + + if (*argv[0] == '-') { + c = *(argv[0] + 1); + + /* + * "--" is the only switch that has no value, stops processing + */ + + if (c == '-' && *(argv[0] + 2) == '\0') { + argv += 1; + argc--; + break; + } + + /* + * So we can check here a switch with no value. + */ + + if (argc == 1) { + Tcl_AppendResult(interp, + "no value given for switch: ", + argv[0], (char *) NULL); + return TCL_ERROR; + } + + if (c == 'c' && strcmp(argv[0] + 1, "context") == 0) { + if (Tcl_GetBoolean(interp, argv[1], &makeContext) != TCL_OK) { + return TCL_ERROR; + } + } else if (c == 'a' && strcmp(argv[0] + 1, "augment") == 0) { + /* + * Augment the current context which implies making a context. + */ + + if (Tcl_GetBoolean(interp, argv[1], &augment) != TCL_OK) { + return TCL_ERROR; + } + makeContext = 1; + } else if (c == 'n' && strcmp(argv[0] + 1, "name") == 0) { + resultName = argv[1]; + } else if (c == 'p' && strcmp(argv[0] + 1,"parent") == 0) { + /* + * Since this implies we are compiling into a context, + * set makeContext here + */ + if (tclOSAGetContextID(OSAComponent, + argv[1], &parentID) != TCL_OK) { + Tcl_AppendResult(interp, "context not found \"", + argv[1], "\"", (char *) NULL); + return TCL_ERROR; + } + makeContext = 1; + } else { + Tcl_AppendResult(interp, "bad option \"", argv[0], + "\": should be -augment, -context, -name or -parent", + (char *) NULL); + return TCL_ERROR; + } + argv += 2; + argc -= 2; + + } else { + break; + } + } + + /* + * Make sure we have some data left... + */ + if (argc == 0) { + goto numArgs; + } + + /* + * Now if we are making a context, see if it is a new one... + * There are three options here: + * 1) There was no name provided, so we autoName it + * 2) There was a name, then check and see if it already exists + * a) If yes, then makeNewContext is false + * b) Otherwise we are making a new context + */ + + if (makeContext) { + modeFlags |= kOSAModeCompileIntoContext; + if (resultName == NULL) { + /* + * Auto name the new context. + */ + resultName = autoName; + resultID = kOSANullScript; + makeNewContext = true; + } else if (tclOSAGetContextID(OSAComponent, + resultName, &resultID) == TCL_OK) { + makeNewContext = false; + } else { + makeNewContext = true; + resultID = kOSANullScript; + } + + /* + * Deal with the augment now... + */ + if (augment && !makeNewContext) { + modeFlags |= kOSAModeAugmentContext; + } + } + + /* + * Ok, now we have the options, so we can compile the script data. + */ + + if (prepareScriptData(argc, argv, &scrptData, &scrptDesc) == TCL_ERROR) { + Tcl_DStringResult(interp, &scrptData); + AEDisposeDesc(&scrptDesc); + return TCL_ERROR; + } + + /* + * If we want to use a parent context, we have to make the context + * by hand. Note, parentID is only specified when you make a new context. + */ + + if (parentID != kOSANullScript && makeNewContext) { + AEDesc contextDesc = { typeNull, NULL }; + + osaErr = OSAMakeContext(OSAComponent->theComponent, + &contextDesc, parentID, &resultID); + modeFlags |= kOSAModeAugmentContext; + } + + osaErr = OSACompile(OSAComponent->theComponent, &scrptDesc, + modeFlags, &resultID); + if (osaErr == noErr) { + + if (makeContext) { + /* + * For the compiled context to be active, you need to run + * the code that is in the context. + */ + OSAID activateID; + + osaErr = OSAExecute(OSAComponent->theComponent, resultID, + resultID, kOSAModeCanInteract, &activateID); + OSADispose(OSAComponent->theComponent, activateID); + + if (osaErr == noErr) { + if (makeNewContext) { + /* + * If we have compiled into a context, + * this is added to the context table + */ + + tclOSAAddContext(OSAComponent, resultName, resultID); + } + + Tcl_SetResult(interp, resultName, TCL_VOLATILE); + tclError = TCL_OK; + } + } else { + /* + * For a script, we return the script name. + */ + tclOSAAddScript(OSAComponent, resultName, modeFlags, resultID); + Tcl_SetResult(interp, resultName, TCL_VOLATILE); + tclError = TCL_OK; + } + } + + /* + * This catches the error either from the original compile, + * or from the execute in case makeContext == true + */ + + if (osaErr == errOSAScriptError) { + OSADispose(OSAComponent->theComponent, resultID); + tclOSAASError(interp, OSAComponent->theComponent, + Tcl_DStringValue(&scrptData)); + tclError = TCL_ERROR; + } else if (osaErr != noErr) { + sprintf(buffer, "Error #%-6d compiling script", osaErr); + Tcl_AppendResult(interp, buffer, (char *) NULL); + tclError = TCL_ERROR; + } + + Tcl_DStringFree(&scrptData); + AEDisposeDesc(&scrptDesc); + + return tclError; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSADecompileCmd -- + * + * This implements the Decompile subcommand of the component command + * + * Results: + * A standard Tcl result. + * + * Side Effects: + * Decompiles the script, and sets interp's result to the + * decompiled script data. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSADecompileCmd( + Tcl_Interp * interp, + tclOSAComponent *OSAComponent, + int argc, + char **argv) +{ + AEDesc resultingSourceData = { typeChar, NULL }; + OSAID scriptID; + Boolean isContext; + long result; + OSErr sysErr = noErr; + + if (argc == 2) { + Tcl_AppendResult(interp, "Wrong # of arguments, should be \"", + argv[0], " ",argv[1], " scriptName \"", (char *) NULL ); + return TCL_ERROR; + } + + if (!(OSAComponent->componentFlags && kOSASupportsGetSource)) { + Tcl_AppendResult(interp, + "Error, this component does not support get source", + (char *) NULL); + return TCL_ERROR; + } + + if (tclOSAGetScriptID(OSAComponent, argv[2], &scriptID) == TCL_OK) { + isContext = false; + } else if (tclOSAGetContextID(OSAComponent, argv[2], &scriptID) + == TCL_OK ) { + isContext = true; + } else { + Tcl_AppendResult(interp, "Could not find script \"", + argv[2], "\"", (char *) NULL); + return TCL_ERROR; + } + + OSAGetScriptInfo(OSAComponent->theComponent, scriptID, + kOSACanGetSource, &result); + + sysErr = OSAGetSource(OSAComponent->theComponent, + scriptID, typeChar, &resultingSourceData); + + if (sysErr == noErr) { + Tcl_DString theResult; + Tcl_DStringInit(&theResult); + + Tcl_DStringAppend(&theResult, *resultingSourceData.dataHandle, + GetHandleSize(resultingSourceData.dataHandle)); + Tcl_DStringResult(interp, &theResult); + AEDisposeDesc(&resultingSourceData); + return TCL_OK; + } else { + Tcl_AppendResult(interp, "Error getting source data", (char *) NULL); + AEDisposeDesc(&resultingSourceData); + return TCL_ERROR; + } +} + +/* + *---------------------------------------------------------------------- + * + * tclOSADeleteCmd -- + * + * This implements the Delete subcommand of the Component command. + * + * Results: + * A standard Tcl result. + * + * Side Effects: + * Deletes a script from the script list of the given component. + * Removes all references to the script, and frees the memory + * associated with it. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSADeleteCmd( + Tcl_Interp *interp, + tclOSAComponent *OSAComponent, + int argc, + char **argv) +{ + char c,*errMsg = NULL; + int length; + + if (argc < 4) { + Tcl_AppendResult(interp, "Wrong # of arguments, should be \"", + argv[0], " ", argv[1], " what scriptName", (char *) NULL); + return TCL_ERROR; + } + + c = *argv[2]; + length = strlen(argv[2]); + if (c == 'c' && strncmp(argv[2], "context", length) == 0) { + if (strcmp(argv[3], "global") == 0) { + Tcl_AppendResult(interp, "You cannot delete the global context", + (char *) NULL); + return TCL_ERROR; + } else if (tclOSADeleteContext(OSAComponent, argv[3]) != TCL_OK) { + Tcl_AppendResult(interp, "Error deleting script \"", argv[2], + "\": ", errMsg, (char *) NULL); + ckfree(errMsg); + return TCL_ERROR; + } + } else if (c == 's' && strncmp(argv[2], "script", length) == 0) { + if (tclOSADeleteScript(OSAComponent, argv[3], errMsg) != TCL_OK) { + Tcl_AppendResult(interp, "Error deleting script \"", argv[3], + "\": ", errMsg, (char *) NULL); + ckfree(errMsg); + return TCL_ERROR; + } + } else { + Tcl_AppendResult(interp,"Unknown value ", argv[2], + " should be one of ", + "\"context\" or \"script\".", + (char *) NULL ); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAExecuteCmd -- + * + * This implements the execute subcommand of the component command. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Executes the given script data, and sets interp's result to + * the OSA component's return value. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSAExecuteCmd( + Tcl_Interp *interp, + tclOSAComponent *OSAComponent, + int argc, + char **argv) +{ + int tclError = TCL_OK, resID = 128; + char c,buffer[32], + *contextName = NULL,*scriptName = NULL, *resName = NULL; + Boolean makeNewContext = false,makeContext = false; + AEDesc scrptDesc = { typeNull, NULL }; + long modeFlags = kOSAModeCanInteract; + OSAID resultID = kOSANullScript, + contextID = kOSANullScript, + parentID = kOSANullScript; + Tcl_DString scrptData; + OSAError osaErr = noErr; + OSErr sysErr = noErr; + + if (argc == 2) { + Tcl_AppendResult(interp, + "Error, no script data for \"", argv[0], + " run\"", (char *) NULL); + return TCL_ERROR; + } + + argv += 2; + argc -= 2; + + /* + * Set the context to the global context by default. + * Then parse the argument list for switches + */ + tclOSAGetContextID(OSAComponent, "global", &contextID); + + while (argc > 0) { + + if (*argv[0] == '-') { + c = *(argv[0] + 1); + + /* + * "--" is the only switch that has no value. + */ + + if (c == '-' && *(argv[0] + 2) == '\0') { + argv += 1; + argc--; + break; + } + + /* + * So we can check here for a switch with no value. + */ + + if (argc == 1) { + Tcl_AppendResult(interp, + "Error, no value given for switch ", + argv[0], (char *) NULL); + return TCL_ERROR; + } + + if (c == 'c' && strcmp(argv[0] + 1, "context") == 0) { + if (tclOSAGetContextID(OSAComponent, + argv[1], &contextID) == TCL_OK) { + } else { + Tcl_AppendResult(interp, "Script context \"", + argv[1], "\" not found", (char *) NULL); + return TCL_ERROR; + } + } else { + Tcl_AppendResult(interp, "Error, invalid switch ", argv[0], + " should be \"-context\"", (char *) NULL); + return TCL_ERROR; + } + + argv += 2; + argc -= 2; + } else { + break; + } + } + + if (argc == 0) { + Tcl_AppendResult(interp, "Error, no script data", (char *) NULL); + return TCL_ERROR; + } + + if (prepareScriptData(argc, argv, &scrptData, &scrptDesc) == TCL_ERROR) { + Tcl_DStringResult(interp, &scrptData); + AEDisposeDesc(&scrptDesc); + return TCL_ERROR; + } + /* + * Now try to compile and run, but check to make sure the + * component supports the one shot deal + */ + if (OSAComponent->componentFlags && kOSASupportsConvenience) { + osaErr = OSACompileExecute(OSAComponent->theComponent, + &scrptDesc, contextID, modeFlags, &resultID); + } else { + /* + * If not, we have to do this ourselves + */ + if (OSAComponent->componentFlags && kOSASupportsCompiling) { + OSAID compiledID = kOSANullScript; + osaErr = OSACompile(OSAComponent->theComponent, &scrptDesc, + modeFlags, &compiledID); + if (osaErr == noErr) { + osaErr = OSAExecute(OSAComponent->theComponent, compiledID, + contextID, modeFlags, &resultID); + } + OSADispose(OSAComponent->theComponent, compiledID); + } else { + /* + * The scripting component had better be able to load text data... + */ + OSAID loadedID = kOSANullScript; + + scrptDesc.descriptorType = OSAComponent->languageID; + osaErr = OSALoad(OSAComponent->theComponent, &scrptDesc, + modeFlags, &loadedID); + if (osaErr == noErr) { + OSAExecute(OSAComponent->theComponent, loadedID, + contextID, modeFlags, &resultID); + } + OSADispose(OSAComponent->theComponent, loadedID); + } + } + if (osaErr == errOSAScriptError) { + tclOSAASError(interp, OSAComponent->theComponent, + Tcl_DStringValue(&scrptData)); + tclError = TCL_ERROR; + } else if (osaErr != noErr) { + sprintf(buffer, "Error #%-6d compiling script", osaErr); + Tcl_AppendResult(interp, buffer, (char *) NULL); + tclError = TCL_ERROR; + } else { + tclOSAResultFromID(interp, OSAComponent->theComponent, resultID); + osaErr = OSADispose(OSAComponent->theComponent, resultID); + tclError = TCL_OK; + } + + Tcl_DStringFree(&scrptData); + AEDisposeDesc(&scrptDesc); + + return tclError; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAInfoCmd -- + * + * This implements the Info subcommand of the component command + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Info on scripts and contexts. See the user documentation for details. + * + *---------------------------------------------------------------------- + */ +static int +tclOSAInfoCmd( + Tcl_Interp *interp, + tclOSAComponent *OSAComponent, + int argc, + char **argv) +{ + char c; + int length; + Tcl_DString theResult; + + if (argc == 2) { + Tcl_AppendResult(interp, "Wrong # of arguments, should be \"", + argv[0], " ", argv[1], " what \"", (char *) NULL ); + return TCL_ERROR; + } + + c = *argv[2]; + length = strlen(argv[2]); + if (c == 's' && strncmp(argv[2], "scripts", length) == 0) { + Tcl_DStringInit(&theResult); + if (argc == 3) { + getSortedHashKeys(&OSAComponent->scriptTable, (char *) NULL, + &theResult); + } else if (argc == 4) { + getSortedHashKeys(&OSAComponent->scriptTable, argv[3], &theResult); + } else { + Tcl_AppendResult(interp, "Error: wrong # of arguments,", + " should be \"", argv[0], " ", argv[1], " ", + argv[2], " ?pattern?", (char *) NULL); + return TCL_ERROR; + } + Tcl_DStringResult(interp, &theResult); + return TCL_OK; + } else if (c == 'c' && strncmp(argv[2], "contexts", length) == 0) { + Tcl_DStringInit(&theResult); + if (argc == 3) { + getSortedHashKeys(&OSAComponent->contextTable, (char *) NULL, + &theResult); + } else if (argc == 4) { + getSortedHashKeys(&OSAComponent->contextTable, + argv[3], &theResult); + } else { + Tcl_AppendResult(interp, "Error: wrong # of arguments for ,", + " should be \"", argv[0], " ", argv[1], " ", + argv[2], " ?pattern?", (char *) NULL); + return TCL_ERROR; + } + Tcl_DStringResult(interp, &theResult); + return TCL_OK; + } else if (c == 'l' && strncmp(argv[2], "language", length) == 0) { + Tcl_SetResult(interp, OSAComponent->languageName, TCL_STATIC); + return TCL_OK; + } else { + Tcl_AppendResult(interp, "Unknown argument \"", argv[2], + "\" for \"", argv[0], " info \", should be one of ", + "\"scripts\" \"language\", or \"contexts\"", + (char *) NULL); + return TCL_ERROR; + } +} + +/* + *---------------------------------------------------------------------- + * + * tclOSALoadCmd -- + * + * This is the load subcommand for the Component Command + * + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Loads script data from the given file, creates a new context + * for it, and sets interp's result to the name of the new context. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSALoadCmd( + Tcl_Interp *interp, + tclOSAComponent *OSAComponent, + int argc, + char **argv) +{ + int tclError = TCL_OK, resID = 128; + char c, autoName[24], + *contextName = NULL, *scriptName = NULL, *resName = NULL; + Boolean makeNewContext = false, makeContext = false; + AEDesc scrptDesc = { typeNull, NULL }; + long modeFlags = kOSAModeCanInteract; + OSAID resultID = kOSANullScript, + contextID = kOSANullScript, + parentID = kOSANullScript; + OSAError osaErr = noErr; + OSErr sysErr = noErr; + long scptInfo; + + autoName[0] = '\0'; + scriptName = autoName; + contextName = autoName; + + if (argc == 2) { + Tcl_AppendResult(interp, + "Error, no data for \"", argv[0], " ", argv[1], + "\"", (char *) NULL); + return TCL_ERROR; + } + + argv += 2; + argc -= 2; + + /* + * Do the argument parsing. + */ + + while (argc > 0) { + + if (*argv[0] == '-') { + c = *(argv[0] + 1); + + /* + * "--" is the only switch that has no value. + */ + + if (c == '-' && *(argv[0] + 2) == '\0') { + argv += 1; + argc--; + break; + } + + /* + * So we can check here a switch with no value. + */ + + if (argc == 1) { + Tcl_AppendResult(interp, "Error, no value given for switch ", + argv[0], (char *) NULL); + return TCL_ERROR; + } + + if (c == 'r' && strcmp(argv[0] + 1, "rsrcname") == 0) { + resName = argv[1]; + } else if (c == 'r' && strcmp(argv[0] + 1, "rsrcid") == 0) { + if (Tcl_GetInt(interp, argv[1], &resID) != TCL_OK) { + Tcl_AppendResult(interp, + "Error getting resource ID", (char *) NULL); + return TCL_ERROR; + } + } else { + Tcl_AppendResult(interp, "Error, invalid switch ", argv[0], + " should be \"--\", \"-rsrcname\" or \"-rsrcid\"", + (char *) NULL); + return TCL_ERROR; + } + + argv += 2; + argc -= 2; + } else { + break; + } + } + /* + * Ok, now we have the options, so we can load the resource, + */ + if (argc == 0) { + Tcl_AppendResult(interp, "Error, no filename given", (char *) NULL); + return TCL_ERROR; + } + + if (tclOSALoad(interp, OSAComponent, resName, resID, + argv[0], &resultID) != TCL_OK) { + Tcl_AppendResult(interp, "Error in load command", (char *) NULL); + return TCL_ERROR; + } + + /* + * Now find out whether we have a script, or a script context. + */ + + OSAGetScriptInfo(OSAComponent->theComponent, resultID, + kOSAScriptIsTypeScriptContext, &scptInfo); + + if (scptInfo) { + autoName[0] = '\0'; + tclOSAAddContext(OSAComponent, autoName, resultID); + + Tcl_SetResult(interp, autoName, TCL_VOLATILE); + } else { + /* + * For a script, we return the script name + */ + autoName[0] = '\0'; + tclOSAAddScript(OSAComponent, autoName, kOSAModeCanInteract, resultID); + Tcl_SetResult(interp, autoName, TCL_VOLATILE); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSARunCmd -- + * + * This implements the run subcommand of the component command + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Runs the given compiled script, and returns the OSA + * component's result. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSARunCmd( + Tcl_Interp *interp, + tclOSAComponent *OSAComponent, + int argc, + char **argv) +{ + int tclError = TCL_OK, + resID = 128; + char c, *contextName = NULL, + *scriptName = NULL, + *resName = NULL; + AEDesc scrptDesc = { typeNull, NULL }; + long modeFlags = kOSAModeCanInteract; + OSAID resultID = kOSANullScript, + contextID = kOSANullScript, + parentID = kOSANullScript; + OSAError osaErr = noErr; + OSErr sysErr = noErr; + char *componentName = argv[0]; + OSAID scriptID; + + if (argc == 2) { + Tcl_AppendResult(interp, "Wrong # of arguments, should be \"", + argv[0], " ", argv[1], " scriptName", (char *) NULL); + return TCL_ERROR; + } + + /* + * Set the context to the global context for this component, + * as a default + */ + if (tclOSAGetContextID(OSAComponent, "global", &contextID) != TCL_OK) { + Tcl_AppendResult(interp, + "Could not find the global context for component ", + OSAComponent->theName, (char *) NULL ); + return TCL_ERROR; + } + + /* + * Now parse the argument list for switches + */ + argv += 2; + argc -= 2; + + while (argc > 0) { + if (*argv[0] == '-') { + c = *(argv[0] + 1); + /* + * "--" is the only switch that has no value + */ + if (c == '-' && *(argv[0] + 2) == '\0') { + argv += 1; + argc--; + break; + } + + /* + * So we can check here for a switch with no value. + */ + if (argc == 1) { + Tcl_AppendResult(interp, "Error, no value given for switch ", + argv[0], (char *) NULL); + return TCL_ERROR; + } + + if (c == 'c' && strcmp(argv[0] + 1, "context") == 0) { + if (argc == 1) { + Tcl_AppendResult(interp, + "Error - no context provided for the -context switch", + (char *) NULL); + return TCL_ERROR; + } else if (tclOSAGetContextID(OSAComponent, + argv[1], &contextID) == TCL_OK) { + } else { + Tcl_AppendResult(interp, "Script context \"", argv[1], + "\" not found", (char *) NULL); + return TCL_ERROR; + } + } else { + Tcl_AppendResult(interp, "Error, invalid switch ", argv[0], + " for ", componentName, + " should be \"-context\"", (char *) NULL); + return TCL_ERROR; + } + argv += 2; + argc -= 2; + } else { + break; + } + } + + if (tclOSAGetScriptID(OSAComponent, argv[0], &scriptID) != TCL_OK) { + if (tclOSAGetContextID(OSAComponent, argv[0], &scriptID) != TCL_OK) { + Tcl_AppendResult(interp, "Could not find script \"", + argv[2], "\"", (char *) NULL); + return TCL_ERROR; + } + } + + sysErr = OSAExecute(OSAComponent->theComponent, + scriptID, contextID, modeFlags, &resultID); + + if (sysErr == errOSAScriptError) { + tclOSAASError(interp, OSAComponent->theComponent, (char *) NULL); + tclError = TCL_ERROR; + } else if (sysErr != noErr) { + char buffer[32]; + sprintf(buffer, "Error #%6.6d encountered in run", sysErr); + Tcl_SetResult(interp, buffer, TCL_VOLATILE); + tclError = TCL_ERROR; + } else { + tclOSAResultFromID(interp, OSAComponent->theComponent, resultID ); + } + OSADispose(OSAComponent->theComponent, resultID); + + return tclError; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAStoreCmd -- + * + * This implements the store subcommand of the component command + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Runs the given compiled script, and returns the OSA + * component's result. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSAStoreCmd( + Tcl_Interp *interp, + tclOSAComponent *OSAComponent, + int argc, + char **argv) +{ + int tclError = TCL_OK, resID = 128; + char c, *contextName = NULL, *scriptName = NULL, *resName = NULL; + Boolean makeNewContext = false, makeContext = false; + AEDesc scrptDesc = { typeNull, NULL }; + long modeFlags = kOSAModeCanInteract; + OSAID resultID = kOSANullScript, + contextID = kOSANullScript, + parentID = kOSANullScript; + OSAError osaErr = noErr; + OSErr sysErr = noErr; + + if (argc == 2) { + Tcl_AppendResult(interp, "Error, no data for \"", argv[0], + " ",argv[1], "\"", (char *) NULL); + return TCL_ERROR; + } + + argv += 2; + argc -= 2; + + /* + * Do the argument parsing + */ + + while (argc > 0) { + if (*argv[0] == '-') { + c = *(argv[0] + 1); + + /* + * "--" is the only switch that has no value + */ + if (c == '-' && *(argv[0] + 2) == '\0') { + argv += 1; + argc--; + break; + } + + /* + * So we can check here a switch with no value. + */ + if (argc == 1) { + Tcl_AppendResult(interp, + "Error, no value given for switch ", + argv[0], (char *) NULL); + return TCL_ERROR; + } + + if (c == 'r' && strcmp(argv[0] + 1, "rsrcname") == 0) { + resName = argv[1]; + } else if (c == 'r' && strcmp(argv[0] + 1, "rsrcid") == 0) { + if (Tcl_GetInt(interp, argv[1], &resID) != TCL_OK) { + Tcl_AppendResult(interp, + "Error getting resource ID", (char *) NULL); + return TCL_ERROR; + } + } else { + Tcl_AppendResult(interp, "Error, invalid switch ", argv[0], + " should be \"--\", \"-rsrcname\" or \"-rsrcid\"", + (char *) NULL); + return TCL_ERROR; + } + + argv += 2; + argc -= 2; + } else { + break; + } + } + /* + * Ok, now we have the options, so we can load the resource, + */ + if (argc != 2) { + Tcl_AppendResult(interp, "Error, wrong # of arguments, should be ", + argv[0], " ", argv[1], "?option flag? scriptName fileName", + (char *) NULL); + return TCL_ERROR; + } + + if (tclOSAStore(interp, OSAComponent, resName, resID, + argv[0], argv[1]) != TCL_OK) { + Tcl_AppendResult(interp, "Error in load command", (char *) NULL); + return TCL_ERROR; + } else { + Tcl_ResetResult(interp); + tclError = TCL_OK; + } + + return tclError; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAMakeNewComponent -- + * + * Makes a command cmdName to represent a new connection to the + * OSA component with componentSubType scriptSubtype. + * + * Results: + * Returns the tclOSAComponent structure for the connection. + * + * Side Effects: + * Adds a new element to the component table. If there is an + * error, then the result of the Tcl interpreter interp is set + * to an appropriate error message. + * + *---------------------------------------------------------------------- + */ + +tclOSAComponent * +tclOSAMakeNewComponent( + Tcl_Interp *interp, + char *cmdName, + char *languageName, + OSType scriptSubtype, + long componentFlags) +{ + char buffer[32]; + AEDesc resultingName = {typeNull, NULL}; + AEDesc nullDesc = {typeNull, NULL }; + OSAID globalContext; + char global[] = "global"; + int nbytes; + ComponentDescription requestedComponent = { + kOSAComponentType, + (OSType) 0, + (OSType) 0, + (long int) 0, + (long int) 0 + }; + Tcl_HashTable *ComponentTable; + Component foundComponent = NULL; + OSAActiveUPP myActiveProcUPP; + + tclOSAComponent *newComponent; + Tcl_HashEntry *hashEntry; + int newPtr; + + requestedComponent.componentSubType = scriptSubtype; + nbytes = sizeof(tclOSAComponent); + newComponent = (tclOSAComponent *) ckalloc(sizeof(tclOSAComponent)); + if (newComponent == NULL) { + goto CleanUp; + } + + foundComponent = FindNextComponent(0, &requestedComponent); + if (foundComponent == 0) { + Tcl_AppendResult(interp, + "Could not find component of requested type", (char *) NULL); + goto CleanUp; + } + + newComponent->theComponent = OpenComponent(foundComponent); + + if (newComponent->theComponent == NULL) { + Tcl_AppendResult(interp, + "Could not open component of the requested type", + (char *) NULL); + goto CleanUp; + } + + newComponent->languageName = (char *) ckalloc(strlen(languageName) + 1); + strcpy(newComponent->languageName,languageName); + + newComponent->componentFlags = componentFlags; + + newComponent->theInterp = interp; + + Tcl_InitHashTable(&newComponent->contextTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&newComponent->scriptTable, TCL_STRING_KEYS); + + if (tclOSAMakeContext(newComponent, global, &globalContext) != TCL_OK) { + sprintf(buffer, "%-6.6d", globalContext); + Tcl_AppendResult(interp, "Error ", buffer, " making ", global, + " context.", (char *) NULL); + goto CleanUp; + } + + newComponent->languageID = scriptSubtype; + + newComponent->theName = (char *) ckalloc(strlen(cmdName) + 1 ); + strcpy(newComponent->theName, cmdName); + + Tcl_CreateCommand(interp, newComponent->theName, Tcl_OSAComponentCmd, + (ClientData) newComponent, tclOSAClose); + + /* + * Register the new component with the component table + */ + + ComponentTable = (Tcl_HashTable *) Tcl_GetAssocData(interp, + "OSAScript_CompTable", (Tcl_InterpDeleteProc **) NULL); + + if (ComponentTable == NULL) { + Tcl_AppendResult(interp, "Error, could not get the Component Table", + " from the Associated data.", (char *) NULL); + return (tclOSAComponent *) NULL; + } + + hashEntry = Tcl_CreateHashEntry(ComponentTable, + newComponent->theName, &newPtr); + Tcl_SetHashValue(hashEntry, (ClientData) newComponent); + + /* + * Set the active proc to call Tcl_DoOneEvent() while idle + */ + if (OSAGetActiveProc(newComponent->theComponent, + &newComponent->defActiveProc, &newComponent->defRefCon) != noErr ) { + /* TODO -- clean up here... */ + } + + myActiveProcUPP = NewOSAActiveProc(TclOSAActiveProc); + OSASetActiveProc(newComponent->theComponent, + myActiveProcUPP, (long) newComponent); + return newComponent; + + CleanUp: + + ckfree((char *) newComponent); + return (tclOSAComponent *) NULL; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAClose -- + * + * This procedure closes the connection to an OSA component, and + * deletes all the script and context data associated with it. + * It is the command deletion callback for the component's command. + * + * Results: + * None + * + * Side effects: + * Closes the connection, and releases all the script data. + * + *---------------------------------------------------------------------- + */ + +void +tclOSAClose( + ClientData clientData) +{ + tclOSAComponent *theComponent = (tclOSAComponent *) clientData; + Tcl_HashEntry *hashEntry; + Tcl_HashSearch search; + tclOSAScript *theScript; + Tcl_HashTable *ComponentTable; + + /* + * Delete the context and script tables + * the memory for the language name, and + * the hash entry. + */ + + for (hashEntry = Tcl_FirstHashEntry(&theComponent->scriptTable, &search); + hashEntry != NULL; + hashEntry = Tcl_NextHashEntry(&search)) { + + theScript = (tclOSAScript *) Tcl_GetHashValue(hashEntry); + OSADispose(theComponent->theComponent, theScript->scriptID); + ckfree((char *) theScript); + Tcl_DeleteHashEntry(hashEntry); + } + + for (hashEntry = Tcl_FirstHashEntry(&theComponent->contextTable, &search); + hashEntry != NULL; + hashEntry = Tcl_NextHashEntry(&search)) { + + Tcl_DeleteHashEntry(hashEntry); + } + + ckfree(theComponent->languageName); + ckfree(theComponent->theName); + + /* + * Finally close the component + */ + + CloseComponent(theComponent->theComponent); + + ComponentTable = (Tcl_HashTable *) + Tcl_GetAssocData(theComponent->theInterp, + "OSAScript_CompTable", (Tcl_InterpDeleteProc **) NULL); + + if (ComponentTable == NULL) { + panic("Error, could not get the Component Table from the Associated data."); + } + + hashEntry = Tcl_FindHashEntry(ComponentTable, theComponent->theName); + if (hashEntry != NULL) { + Tcl_DeleteHashEntry(hashEntry); + } + + ckfree((char *) theComponent); +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAGetContextID -- + * + * This returns the context ID, given the component name. + * + * Results: + * A context ID + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static int +tclOSAGetContextID( + tclOSAComponent *theComponent, + char *contextName, + OSAID *theContext) +{ + Tcl_HashEntry *hashEntry; + tclOSAContext *contextStruct; + + if ((hashEntry = Tcl_FindHashEntry(&theComponent->contextTable, + contextName)) == NULL ) { + return TCL_ERROR; + } else { + contextStruct = (tclOSAContext *) Tcl_GetHashValue(hashEntry); + *theContext = contextStruct->contextID; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAAddContext -- + * + * This adds the context ID, with the name contextName. If the + * name is passed in as a NULL string, space is malloc'ed for the + * string and a new name is made up, if the string is empty, you + * must have allocated enough space ( 24 characters is fine) for + * the name, which is made up and passed out. + * + * Results: + * Nothing + * + * Side effects: + * Adds the script context to the component's context table. + * + *---------------------------------------------------------------------- + */ + +static void +tclOSAAddContext( + tclOSAComponent *theComponent, + char *contextName, + const OSAID theContext) +{ + static unsigned short contextIndex = 0; + tclOSAContext *contextStruct; + Tcl_HashEntry *hashEntry; + int newPtr; + + if (contextName == NULL) { + contextName = ckalloc(24 * sizeof(char)); + sprintf(contextName, "OSAContext%d", contextIndex++); + } else if (*contextName == '\0') { + sprintf(contextName, "OSAContext%d", contextIndex++); + } + + hashEntry = Tcl_CreateHashEntry(&theComponent->contextTable, + contextName, &newPtr); + + contextStruct = (tclOSAContext *) ckalloc(sizeof(tclOSAContext)); + contextStruct->contextID = theContext; + Tcl_SetHashValue(hashEntry,(ClientData) contextStruct); +} + +/* + *---------------------------------------------------------------------- + * + * tclOSADeleteContext -- + * + * This deletes the context struct, with the name contextName. + * + * Results: + * A normal Tcl result + * + * Side effects: + * Removes the script context to the component's context table, + * and deletes the data associated with it. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSADeleteContext( + tclOSAComponent *theComponent, + char *contextName) +{ + Tcl_HashEntry *hashEntry; + tclOSAContext *contextStruct; + + hashEntry = Tcl_FindHashEntry(&theComponent->contextTable, contextName); + if (hashEntry == NULL) { + return TCL_ERROR; + } + /* + * Dispose of the script context data + */ + contextStruct = (tclOSAContext *) Tcl_GetHashValue(hashEntry); + OSADispose(theComponent->theComponent,contextStruct->contextID); + /* + * Then the hash entry + */ + ckfree((char *) contextStruct); + Tcl_DeleteHashEntry(hashEntry); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAMakeContext -- + * + * This makes the context with name contextName, and returns the ID. + * + * Results: + * A standard Tcl result + * + * Side effects: + * Makes a new context, adds it to the context table, and returns + * the new contextID in the variable theContext. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSAMakeContext( + tclOSAComponent *theComponent, + char *contextName, + OSAID *theContext) +{ + AEDesc contextNameDesc = {typeNull, NULL}; + OSAError osaErr = noErr; + + AECreateDesc(typeChar, contextName, strlen(contextName), &contextNameDesc); + osaErr = OSAMakeContext(theComponent->theComponent, &contextNameDesc, + kOSANullScript, theContext); + + AEDisposeDesc(&contextNameDesc); + + if (osaErr == noErr) { + tclOSAAddContext(theComponent, contextName, *theContext); + } else { + *theContext = (OSAID) osaErr; + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAStore -- + * + * This stores a script resource from the file named in fileName. + * + * Most of this routine is caged from the Tcl Source, from the + * Tcl_MacSourceCmd routine. This is good, since it ensures this + * follows the same convention for looking up files as Tcl. + * + * Returns + * A standard Tcl result. + * + * Side Effects: + * The given script data is stored in the file fileName. + * + *---------------------------------------------------------------------- + */ + +int +tclOSAStore( + Tcl_Interp *interp, + tclOSAComponent *theComponent, + char *resourceName, + int resourceNumber, + char *scriptName, + char *fileName) +{ + Handle resHandle; + Str255 rezName; + int result = TCL_OK; + short saveRef, fileRef = -1; + char idStr[64]; + FSSpec fileSpec; + Tcl_DString buffer; + char *nativeName; + OSErr myErr = noErr; + OSAID scriptID; + Size scriptSize; + AEDesc scriptData; + + /* + * First extract the script data + */ + + if (tclOSAGetScriptID(theComponent, scriptName, &scriptID) != TCL_OK ) { + if (tclOSAGetContextID(theComponent, scriptName, &scriptID) + != TCL_OK) { + Tcl_AppendResult(interp, "Error getting script ", + scriptName, (char *) NULL); + return TCL_ERROR; + } + } + + myErr = OSAStore(theComponent->theComponent, scriptID, + typeOSAGenericStorage, kOSAModeNull, &scriptData); + if (myErr != noErr) { + sprintf(idStr, "%d", myErr); + Tcl_AppendResult(interp, "Error #", idStr, + " storing script ", scriptName, (char *) NULL); + return TCL_ERROR; + } + + /* + * Now try to open the output file + */ + + saveRef = CurResFile(); + + if (fileName != NULL) { + OSErr err; + + Tcl_DStringInit(&buffer); + nativeName = Tcl_TranslateFileName(interp, fileName, &buffer); + if (nativeName == NULL) { + return TCL_ERROR; + } + err = FSpLocationFromPath(strlen(nativeName), nativeName, &fileSpec); + + Tcl_DStringFree(&buffer); + if ((err != noErr) && (err != fnfErr)) { + Tcl_AppendResult(interp, + "Error getting a location for the file: \"", + fileName, "\".", NULL); + return TCL_ERROR; + } + + FSpCreateResFileCompat(&fileSpec, + 'WiSH', 'osas', smSystemScript); + myErr = ResError(); + + if ((myErr != noErr) && (myErr != dupFNErr)) { + sprintf(idStr, "%d", myErr); + Tcl_AppendResult(interp, "Error #", idStr, + " creating new resource file ", fileName, (char *) NULL); + result = TCL_ERROR; + goto rezEvalCleanUp; + } + + fileRef = FSpOpenResFileCompat(&fileSpec, fsRdWrPerm); + if (fileRef == -1) { + Tcl_AppendResult(interp, "Error reading the file: \"", + fileName, "\".", NULL); + result = TCL_ERROR; + goto rezEvalCleanUp; + } + UseResFile(fileRef); + } else { + /* + * The default behavior will search through all open resource files. + * This may not be the behavior you desire. If you want the behavior + * of this call to *only* search the application resource fork, you + * must call UseResFile at this point to set it to the application + * file. This means you must have already obtained the application's + * fileRef when the application started up. + */ + } + + /* + * Load the resource by name + */ + if (resourceName != NULL) { + strcpy((char *) rezName + 1, resourceName); + rezName[0] = strlen(resourceName); + resHandle = Get1NamedResource('scpt', rezName); + myErr = ResError(); + if (resHandle == NULL) { + /* + * These signify either the resource or the resource + * type were not found + */ + if (myErr == resNotFound || myErr == noErr) { + short uniqueID; + while ((uniqueID = Unique1ID('scpt') ) < 128) {} + AddResource(scriptData.dataHandle, 'scpt', uniqueID, rezName); + WriteResource(resHandle); + result = TCL_OK; + goto rezEvalCleanUp; + } else { + /* + * This means there was some other error, for now + * I just bag out. + */ + sprintf(idStr, "%d", myErr); + Tcl_AppendResult(interp, "Error #", idStr, + " opening scpt resource named ", resourceName, + " in file ", fileName, (char *) NULL); + result = TCL_ERROR; + goto rezEvalCleanUp; + } + } + /* + * Or ID + */ + } else { + resHandle = Get1Resource('scpt', resourceNumber); + rezName[0] = 0; + rezName[1] = '\0'; + myErr = ResError(); + if (resHandle == NULL) { + /* + * These signify either the resource or the resource + * type were not found + */ + if (myErr == resNotFound || myErr == noErr) { + AddResource(scriptData.dataHandle, 'scpt', + resourceNumber, rezName); + WriteResource(resHandle); + result = TCL_OK; + goto rezEvalCleanUp; + } else { + /* + * This means there was some other error, for now + * I just bag out */ + sprintf(idStr, "%d", myErr); + Tcl_AppendResult(interp, "Error #", idStr, + " opening scpt resource named ", resourceName, + " in file ", fileName,(char *) NULL); + result = TCL_ERROR; + goto rezEvalCleanUp; + } + } + } + + /* + * We get to here if the resource exists + * we just copy into it... + */ + + scriptSize = GetHandleSize(scriptData.dataHandle); + SetHandleSize(resHandle, scriptSize); + HLock(scriptData.dataHandle); + HLock(resHandle); + BlockMove(*scriptData.dataHandle, *resHandle,scriptSize); + HUnlock(scriptData.dataHandle); + HUnlock(resHandle); + ChangedResource(resHandle); + WriteResource(resHandle); + result = TCL_OK; + goto rezEvalCleanUp; + + rezEvalError: + sprintf(idStr, "ID=%d", resourceNumber); + Tcl_AppendResult(interp, "The resource \"", + (resourceName != NULL ? resourceName : idStr), + "\" could not be loaded from ", + (fileName != NULL ? fileName : "application"), + ".", NULL); + + rezEvalCleanUp: + if (fileRef != -1) { + CloseResFile(fileRef); + } + + UseResFile(saveRef); + + return result; +} + +/*---------------------------------------------------------------------- + * + * tclOSALoad -- + * + * This loads a script resource from the file named in fileName. + * Most of this routine is caged from the Tcl Source, from the + * Tcl_MacSourceCmd routine. This is good, since it ensures this + * follows the same convention for looking up files as Tcl. + * + * Returns + * A standard Tcl result. + * + * Side Effects: + * A new script element is created from the data in the file. + * The script ID is passed out in the variable resultID. + * + *---------------------------------------------------------------------- + */ + +int +tclOSALoad( + Tcl_Interp *interp, + tclOSAComponent *theComponent, + char *resourceName, + int resourceNumber, + char *fileName, + OSAID *resultID) +{ + Handle sourceData; + Str255 rezName; + int result = TCL_OK; + short saveRef, fileRef = -1; + char idStr[64]; + FSSpec fileSpec; + Tcl_DString buffer; + char *nativeName; + + saveRef = CurResFile(); + + if (fileName != NULL) { + OSErr err; + + Tcl_DStringInit(&buffer); + nativeName = Tcl_TranslateFileName(interp, fileName, &buffer); + if (nativeName == NULL) { + return TCL_ERROR; + } + err = FSpLocationFromPath(strlen(nativeName), nativeName, &fileSpec); + Tcl_DStringFree(&buffer); + if (err != noErr) { + Tcl_AppendResult(interp, "Error finding the file: \"", + fileName, "\".", NULL); + return TCL_ERROR; + } + + fileRef = FSpOpenResFileCompat(&fileSpec, fsRdPerm); + if (fileRef == -1) { + Tcl_AppendResult(interp, "Error reading the file: \"", + fileName, "\".", NULL); + return TCL_ERROR; + } + UseResFile(fileRef); + } else { + /* + * The default behavior will search through all open resource files. + * This may not be the behavior you desire. If you want the behavior + * of this call to *only* search the application resource fork, you + * must call UseResFile at this point to set it to the application + * file. This means you must have already obtained the application's + * fileRef when the application started up. + */ + } + + /* + * Load the resource by name or ID + */ + if (resourceName != NULL) { + strcpy((char *) rezName + 1, resourceName); + rezName[0] = strlen(resourceName); + sourceData = GetNamedResource('scpt', rezName); + } else { + sourceData = GetResource('scpt', (short) resourceNumber); + } + + if (sourceData == NULL) { + result = TCL_ERROR; + } else { + AEDesc scriptDesc; + OSAError osaErr; + + scriptDesc.descriptorType = typeOSAGenericStorage; + scriptDesc.dataHandle = sourceData; + + osaErr = OSALoad(theComponent->theComponent, &scriptDesc, + kOSAModeNull, resultID); + + ReleaseResource(sourceData); + + if (osaErr != noErr) { + result = TCL_ERROR; + goto rezEvalError; + } + + goto rezEvalCleanUp; + } + + rezEvalError: + sprintf(idStr, "ID=%d", resourceNumber); + Tcl_AppendResult(interp, "The resource \"", + (resourceName != NULL ? resourceName : idStr), + "\" could not be loaded from ", + (fileName != NULL ? fileName : "application"), + ".", NULL); + + rezEvalCleanUp: + if (fileRef != -1) { + CloseResFile(fileRef); + } + + UseResFile(saveRef); + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAGetScriptID -- + * + * This returns the context ID, gibven the component name. + * + * Results: + * A standard Tcl result + * + * Side effects: + * Passes out the script ID in the variable scriptID. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSAGetScriptID( + tclOSAComponent *theComponent, + char *scriptName, + OSAID *scriptID) +{ + tclOSAScript *theScript; + + theScript = tclOSAGetScript(theComponent, scriptName); + if (theScript == NULL) { + return TCL_ERROR; + } + + *scriptID = theScript->scriptID; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAAddScript -- + * + * This adds a script to theComponent's script table, with the + * given name & ID. + * + * Results: + * A standard Tcl result + * + * Side effects: + * Adds an element to the component's script table. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSAAddScript( + tclOSAComponent *theComponent, + char *scriptName, + long modeFlags, + OSAID scriptID) +{ + Tcl_HashEntry *hashEntry; + int newPtr; + static int scriptIndex = 0; + tclOSAScript *theScript; + + if (*scriptName == '\0') { + sprintf(scriptName, "OSAScript%d", scriptIndex++); + } + + hashEntry = Tcl_CreateHashEntry(&theComponent->scriptTable, + scriptName, &newPtr); + if (newPtr == 0) { + theScript = (tclOSAScript *) Tcl_GetHashValue(hashEntry); + OSADispose(theComponent->theComponent, theScript->scriptID); + } else { + theScript = (tclOSAScript *) ckalloc(sizeof(tclOSAScript)); + if (theScript == NULL) { + return TCL_ERROR; + } + } + + theScript->scriptID = scriptID; + theScript->languageID = theComponent->languageID; + theScript->modeFlags = modeFlags; + + Tcl_SetHashValue(hashEntry,(ClientData) theScript); + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAGetScriptID -- + * + * This returns the script structure, given the component and script name. + * + * Results: + * A pointer to the script structure. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static tclOSAScript * +tclOSAGetScript( + tclOSAComponent *theComponent, + char *scriptName) +{ + Tcl_HashEntry *hashEntry; + + hashEntry = Tcl_FindHashEntry(&theComponent->scriptTable, scriptName); + if (hashEntry == NULL) { + return NULL; + } + + return (tclOSAScript *) Tcl_GetHashValue(hashEntry); +} + +/* + *---------------------------------------------------------------------- + * + * tclOSADeleteScript -- + * + * This deletes the script given by scriptName. + * + * Results: + * A standard Tcl result + * + * Side effects: + * Deletes the script from the script table, and frees up the + * resources associated with it. If there is an error, then + * space for the error message is malloc'ed, and passed out in + * the variable errMsg. + * + *---------------------------------------------------------------------- + */ + +static int +tclOSADeleteScript( + tclOSAComponent *theComponent, + char *scriptName, + char *errMsg) +{ + Tcl_HashEntry *hashEntry; + tclOSAScript *scriptPtr; + + hashEntry = Tcl_FindHashEntry(&theComponent->scriptTable, scriptName); + if (hashEntry == NULL) { + errMsg = ckalloc(17); + strcpy(errMsg,"Script not found"); + return TCL_ERROR; + } + + scriptPtr = (tclOSAScript *) Tcl_GetHashValue(hashEntry); + OSADispose(theComponent->theComponent, scriptPtr->scriptID); + ckfree((char *) scriptPtr); + Tcl_DeleteHashEntry(hashEntry); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclOSAActiveProc -- + * + * This is passed to each component. It is run periodically + * during script compilation and script execution. It in turn + * calls Tcl_DoOneEvent to process the event queue. We also call + * the default Active proc which will let the user cancel the script + * by hitting Command-. + * + * Results: + * A standard MacOS system error + * + * Side effects: + * Any Tcl code may run while calling Tcl_DoOneEvent. + * + *---------------------------------------------------------------------- + */ + +static pascal OSErr +TclOSAActiveProc( + long refCon) +{ + tclOSAComponent *theComponent = (tclOSAComponent *) refCon; + + Tcl_DoOneEvent(TCL_DONT_WAIT); + CallOSAActiveProc(theComponent->defActiveProc, theComponent->defRefCon); + + return noErr; +} + +/* + *---------------------------------------------------------------------- + * + * ASCIICompareProc -- + * + * Trivial ascii compare for use with qsort. + * + * Results: + * strcmp of the two input strings + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ +static int +ASCIICompareProc(const void *first,const void *second) +{ + int order; + + char *firstString = *((char **) first); + char *secondString = *((char **) second); + + order = strcmp(firstString, secondString); + + return order; +} + +#define REALLOC_INCR 30 +/* + *---------------------------------------------------------------------- + * + * getSortedHashKeys -- + * + * returns an alphabetically sorted list of the keys of the hash + * theTable which match the string "pattern" in the DString + * theResult. pattern == NULL matches all. + * + * Results: + * None + * + * Side effects: + * ReInitializes the DString theResult, then copies the names of + * the matching keys into the string as list elements. + * + *---------------------------------------------------------------------- + */ + +static void +getSortedHashKeys( + Tcl_HashTable *theTable, + char *pattern, + Tcl_DString *theResult) +{ + Tcl_HashSearch search; + Tcl_HashEntry *hPtr; + Boolean compare = true; + char *keyPtr; + static char **resultArgv = NULL; + static int totSize = 0; + int totElem = 0, i; + + if (pattern == NULL || *pattern == '\0' || + (*pattern == '*' && *(pattern + 1) == '\0')) { + compare = false; + } + + for (hPtr = Tcl_FirstHashEntry(theTable,&search), totElem = 0; + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + + keyPtr = (char *) Tcl_GetHashKey(theTable, hPtr); + if (!compare || Tcl_StringMatch(keyPtr, pattern)) { + totElem++; + if (totElem >= totSize) { + totSize += REALLOC_INCR; + resultArgv = (char **) ckrealloc((char *) resultArgv, + totSize * sizeof(char *)); + } + resultArgv[totElem - 1] = keyPtr; + } + } + + Tcl_DStringInit(theResult); + if (totElem == 1) { + Tcl_DStringAppendElement(theResult, resultArgv[0]); + } else if (totElem > 1) { + qsort((VOID *) resultArgv, (size_t) totElem, sizeof (char *), + ASCIICompareProc); + + for (i = 0; i < totElem; i++) { + Tcl_DStringAppendElement(theResult, resultArgv[i]); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * prepareScriptData -- + * + * Massages the input data in the argv array, concating the + * elements, with a " " between each, and replacing \n with \r, + * and \\n with " ". Puts the result in the the DString scrptData, + * and copies the result to the AEdesc scrptDesc. + * + * Results: + * Standard Tcl result + * + * Side effects: + * Creates a new Handle (with AECreateDesc) for the script data. + * Stores the script in scrptData, or the error message if there + * is an error creating the descriptor. + * + *---------------------------------------------------------------------- + */ + +static int +prepareScriptData( + int argc, + char **argv, + Tcl_DString *scrptData, + AEDesc *scrptDesc) +{ + char * ptr; + int i; + char buffer[7]; + OSErr sysErr = noErr; + + Tcl_DStringInit(scrptData); + + for (i = 0; i < argc; i++) { + Tcl_DStringAppend(scrptData, argv[i], -1); + Tcl_DStringAppend(scrptData, " ", 1); + } + + /* + * First replace the \n's with \r's in the script argument + * Also replace "\\n" with " ". + */ + + for (ptr = scrptData->string; *ptr != '\0'; ptr++) { + if (*ptr == '\n') { + *ptr = '\r'; + } else if (*ptr == '\\') { + if (*(ptr + 1) == '\n') { + *ptr = ' '; + *(ptr + 1) = ' '; + } + } + } + + sysErr = AECreateDesc(typeChar, Tcl_DStringValue(scrptData), + Tcl_DStringLength(scrptData), scrptDesc); + + if (sysErr != noErr) { + sprintf(buffer, "%6d", sysErr); + Tcl_DStringFree(scrptData); + Tcl_DStringAppend(scrptData, "Error #", 7); + Tcl_DStringAppend(scrptData, buffer, -1); + Tcl_DStringAppend(scrptData, " creating Script Data Descriptor.", 33); + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAResultFromID -- + * + * Gets a human readable version of the result from the script ID + * and returns it in the result of the interpreter interp + * + * Results: + * None + * + * Side effects: + * Sets the result of interp to the human readable version of resultID. + * + * + *---------------------------------------------------------------------- + */ + +void +tclOSAResultFromID( + Tcl_Interp *interp, + ComponentInstance theComponent, + OSAID resultID ) +{ + OSErr myErr = noErr; + AEDesc resultDesc; + Tcl_DString resultStr; + + Tcl_DStringInit(&resultStr); + + myErr = OSADisplay(theComponent, resultID, typeChar, + kOSAModeNull, &resultDesc); + Tcl_DStringAppend(&resultStr, (char *) *resultDesc.dataHandle, + GetHandleSize(resultDesc.dataHandle)); + Tcl_DStringResult(interp,&resultStr); +} + +/* + *---------------------------------------------------------------------- + * + * tclOSAASError -- + * + * Gets the error message from the AppleScript component, and adds + * it to interp's result. If the script data is known, will point + * out the offending bit of code. This MUST BE A NULL TERMINATED + * C-STRING, not a typeChar. + * + * Results: + * None + * + * Side effects: + * Sets the result of interp to error, plus the relevant portion + * of the script. + * + *---------------------------------------------------------------------- + */ + +void +tclOSAASError( + Tcl_Interp * interp, + ComponentInstance theComponent, + char *scriptData ) +{ + OSErr myErr = noErr; + AEDesc errResult,errLimits; + Tcl_DString errStr; + DescType returnType; + Size returnSize; + short srcStart,srcEnd; + char buffer[16]; + + Tcl_DStringInit(&errStr); + Tcl_DStringAppend(&errStr, "An AppleScript error was encountered.\n", -1); + + OSAScriptError(theComponent, kOSAErrorNumber, + typeShortInteger, &errResult); + + sprintf(buffer, "Error #%-6.6d\n", (short int) **errResult.dataHandle); + + AEDisposeDesc(&errResult); + + Tcl_DStringAppend(&errStr,buffer, 15); + + OSAScriptError(theComponent, kOSAErrorMessage, typeChar, &errResult); + Tcl_DStringAppend(&errStr, (char *) *errResult.dataHandle, + GetHandleSize(errResult.dataHandle)); + AEDisposeDesc(&errResult); + + if (scriptData != NULL) { + int lowerB, upperB; + + myErr = OSAScriptError(theComponent, kOSAErrorRange, + typeOSAErrorRange, &errResult); + + myErr = AECoerceDesc(&errResult, typeAERecord, &errLimits); + myErr = AEGetKeyPtr(&errLimits, keyOSASourceStart, + typeShortInteger, &returnType, &srcStart, + sizeof(short int), &returnSize); + myErr = AEGetKeyPtr(&errLimits, keyOSASourceEnd, typeShortInteger, + &returnType, &srcEnd, sizeof(short int), &returnSize); + AEDisposeDesc(&errResult); + AEDisposeDesc(&errLimits); + + Tcl_DStringAppend(&errStr, "\nThe offending bit of code was:\n\t", -1); + /* + * Get the full line on which the error occured: + */ + for (lowerB = srcStart; lowerB > 0; lowerB--) { + if (*(scriptData + lowerB ) == '\r') { + lowerB++; + break; + } + } + + for (upperB = srcEnd; *(scriptData + upperB) != '\0'; upperB++) { + if (*(scriptData + upperB) == '\r') { + break; + } + } + + Tcl_DStringAppend(&errStr, scriptData+lowerB, srcStart - lowerB); + Tcl_DStringAppend(&errStr, "_", 1); + Tcl_DStringAppend(&errStr, scriptData+srcStart, upperB - srcStart); + } + + Tcl_DStringResult(interp,&errStr); +} + +/* + *---------------------------------------------------------------------- + * + * GetRawDataFromDescriptor -- + * + * Get the data from a descriptor. + * + * Results: + * None + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +GetRawDataFromDescriptor( + AEDesc *theDesc, + Ptr destPtr, + Size destMaxSize, + Size *actSize) + { + Size copySize; + + if (theDesc->dataHandle) { + HLock((Handle)theDesc->dataHandle); + *actSize = GetHandleSize((Handle)theDesc->dataHandle); + copySize = *actSize < destMaxSize ? *actSize : destMaxSize; + BlockMove(*theDesc->dataHandle, destPtr, copySize); + HUnlock((Handle)theDesc->dataHandle); + } else { + *actSize = 0; + } + + } + +/* + *---------------------------------------------------------------------- + * + * GetRawDataFromDescriptor -- + * + * Get the data from a descriptor. Assume it's a C string. + * + * Results: + * None + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static OSErr +GetCStringFromDescriptor( + AEDesc *sourceDesc, + char *resultStr, + Size resultMaxSize, + Size *resultSize) +{ + OSErr err; + AEDesc resultDesc; + + resultDesc.dataHandle = nil; + + err = AECoerceDesc(sourceDesc, typeChar, &resultDesc); + + if (!err) { + GetRawDataFromDescriptor(&resultDesc, (Ptr) resultStr, + resultMaxSize - 1, resultSize); + resultStr[*resultSize] = 0; + } else { + err = errAECoercionFail; + } + + if (resultDesc.dataHandle) { + AEDisposeDesc(&resultDesc); + } + + return err; +} diff --git a/tcl/mac/tclMacOSA.exp b/tcl/mac/tclMacOSA.exp new file mode 100644 index 00000000000..4cde51227f0 --- /dev/null +++ b/tcl/mac/tclMacOSA.exp @@ -0,0 +1 @@ +Tclapplescript_Init diff --git a/tcl/mac/tclMacOSA.r b/tcl/mac/tclMacOSA.r new file mode 100644 index 00000000000..82938bf07ab --- /dev/null +++ b/tcl/mac/tclMacOSA.r @@ -0,0 +1,76 @@ +/* + * tkMacOSA.r -- + * + * This file creates resources used by the AppleScript package. + * + * Copyright (c) 1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <Types.r> +#include <SysTypes.r> + +/* + * The folowing include and defines help construct + * the version string for Tcl. + */ + +#define SCRIPT_MAJOR_VERSION 1 /* Major number */ +#define SCRIPT_MINOR_VERSION 0 /* Minor number */ +#define SCRIPT_RELEASE_SERIAL 2 /* Really minor number! */ +#define RELEASE_LEVEL alpha /* alpha, beta, or final */ +#define SCRIPT_VERSION "1.0" +#define SCRIPT_PATCH_LEVEL "1.0a2" +#define FINAL 0 /* Change to 1 if final version. */ + +#if FINAL +# define MINOR_VERSION (SCRIPT_MINOR_VERSION * 16) + SCRIPT_RELEASE_SERIAL +#else +# define MINOR_VERSION SCRIPT_MINOR_VERSION * 16 +#endif + +#define RELEASE_CODE 0x00 + +resource 'vers' (1) { + SCRIPT_MAJOR_VERSION, MINOR_VERSION, + RELEASE_LEVEL, 0x00, verUS, + SCRIPT_PATCH_LEVEL, + SCRIPT_PATCH_LEVEL ", by Jim Ingham & Ray Johnson © Sun Microsystems" +}; + +resource 'vers' (2) { + SCRIPT_MAJOR_VERSION, MINOR_VERSION, + RELEASE_LEVEL, 0x00, verUS, + SCRIPT_PATCH_LEVEL, + "Tclapplescript " SCRIPT_PATCH_LEVEL " © 1996-1997" +}; + +/* + * The -16397 string will be displayed by Finder when a user + * tries to open the shared library. The string should + * give the user a little detail about the library's capabilities + * and enough information to install the library in the correct location. + * A similar string should be placed in all shared libraries. + */ +resource 'STR ' (-16397, purgeable) { + "TclAppleScript Library\n\n" + "This library provides the ability to run AppleScript " + " commands from Tcl/Tk programs. To work properly, it " + "should be placed in the ÔTool Command LanguageÕ folder " + "within the Extensions folder." +}; + + +/* + * We now load the Tk library into the resource fork of the library. + */ + +data 'TEXT' (4000,"pkgIndex",purgeable, preload) { + "# Tcl package index file, version 1.0\n" + "package ifneeded Tclapplescript 1.0 [list tclPkgSetup $dir Tclapplescript 1.0 {{Tclapplescript" + ".shlb load AppleScript}}]\n" +}; diff --git a/tcl/mac/tclMacPanic.c b/tcl/mac/tclMacPanic.c new file mode 100644 index 00000000000..f059f4aee6b --- /dev/null +++ b/tcl/mac/tclMacPanic.c @@ -0,0 +1,235 @@ +/* + * tclMacPanic.c -- + * + * Source code for the "panic" library procedure used in "Simple Shell"; + * other Mac applications will probably override this with a more robust + * application-specific panic procedure. + * + * Copyright (c) 1993-1994 Lockheed Missle & Space Company, AI Center + * Copyright (c) 1995-1996 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + + +#include <Events.h> +#include <Controls.h> +#include <Windows.h> +#include <TextEdit.h> +#include <Fonts.h> +#include <Dialogs.h> +#include <Icons.h> +#include <Sound.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +#include "tclInt.h" + +/* + * constants for panic dialog + */ +#define PANICHEIGHT 150 /* Height of dialog */ +#define PANICWIDTH 350 /* Width of dialog */ +#define PANIC_BUTTON_RECT {125, 260, 145, 335} /* Rect for button. */ +#define PANIC_ICON_RECT {10, 20, 42, 52} /* Rect for icon. */ +#define PANIC_TEXT_RECT {10, 65, 140, 330} /* Rect for text. */ +#define ENTERCODE (0x03) +#define RETURNCODE (0x0D) + +/* + * The panicProc variable contains a pointer to an application + * specific panic procedure. + */ + +void (*panicProc) _ANSI_ARGS_(TCL_VARARGS(char *,format)) = NULL; + +/* + *---------------------------------------------------------------------- + * + * Tcl_SetPanicProc -- + * + * Replace the default panic behavior with the specified functiion. + * + * Results: + * None. + * + * Side effects: + * Sets the panicProc variable. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_SetPanicProc(proc) + void (*proc) _ANSI_ARGS_(TCL_VARARGS(char *,format)); +{ + panicProc = proc; +} + +/* + *---------------------------------------------------------------------- + * + * MacPanic -- + * + * Displays panic info.. + * + * Results: + * None. + * + * Side effects: + * Sets the panicProc variable. + * + *---------------------------------------------------------------------- + */ + +static void +MacPanic( + char *msg) /* Text to show in panic dialog. */ +{ + WindowRef macWinPtr, foundWinPtr; + Rect macRect; + Rect buttonRect = PANIC_BUTTON_RECT; + Rect iconRect = PANIC_ICON_RECT; + Rect textRect = PANIC_TEXT_RECT; + ControlHandle okButtonHandle; + EventRecord event; + Handle stopIconHandle; + int part; + Boolean done = false; + + + /* + * Put up an alert without using the Resource Manager (there may + * be no resources to load). Use the Window and Control Managers instead. + * We want the window centered on the main monitor. The following + * should be tested with multiple monitors. Look and see if there is a way + * not using qd.screenBits. + */ + + macRect.top = (qd.screenBits.bounds.top + qd.screenBits.bounds.bottom) + / 2 - (PANICHEIGHT / 2); + macRect.bottom = (qd.screenBits.bounds.top + qd.screenBits.bounds.bottom) + / 2 + (PANICHEIGHT / 2); + macRect.left = (qd.screenBits.bounds.left + qd.screenBits.bounds.right) + / 2 - (PANICWIDTH / 2); + macRect.right = (qd.screenBits.bounds.left + qd.screenBits.bounds.right) + / 2 + (PANICWIDTH / 2); + + macWinPtr = NewWindow(NULL, &macRect, "\p", true, dBoxProc, (WindowRef) -1, + false, 0); + if (macWinPtr == NULL) { + goto exitNow; + } + + okButtonHandle = NewControl(macWinPtr, &buttonRect, "\pOK", true, + 0, 0, 1, pushButProc, 0); + if (okButtonHandle == NULL) { + CloseWindow(macWinPtr); + goto exitNow; + } + + SelectWindow(macWinPtr); + SetCursor(&qd.arrow); + stopIconHandle = GetIcon(kStopIcon); + + while (!done) { + if (WaitNextEvent(mDownMask | keyDownMask | updateMask, + &event, 0, NULL)) { + switch(event.what) { + case mouseDown: + part = FindWindow(event.where, &foundWinPtr); + + if ((foundWinPtr != macWinPtr) || (part != inContent)) { + SysBeep(1); + } else { + SetPortWindowPort(macWinPtr); + GlobalToLocal(&event.where); + part = FindControl(event.where, macWinPtr, + &okButtonHandle); + + if ((inButton == part) && + (TrackControl(okButtonHandle, + event.where, NULL))) { + done = true; + } + } + break; + case keyDown: + switch (event.message & charCodeMask) { + case ENTERCODE: + case RETURNCODE: + HiliteControl(okButtonHandle, 1); + HiliteControl(okButtonHandle, 0); + done = true; + } + break; + case updateEvt: + SetPortWindowPort(macWinPtr); + TextFont(systemFont); + + BeginUpdate(macWinPtr); + if (stopIconHandle != NULL) { + PlotIcon(&iconRect, stopIconHandle); + } + TextBox(msg, strlen(msg), &textRect, teFlushDefault); + DrawControls(macWinPtr); + EndUpdate(macWinPtr); + } + } + } + + CloseWindow(macWinPtr); + + exitNow: +#ifdef TCL_DEBUG + Debugger(); +#else + abort(); +#endif +} + +/* + *---------------------------------------------------------------------- + * + * panic -- + * + * Print an error message and kill the process. + * + * Results: + * None. + * + * Side effects: + * The process dies, entering the debugger if possible. + * + *---------------------------------------------------------------------- + */ + +#pragma ignore_oldstyle on +void +panic(char * format, ...) +{ + va_list varg; + char errorText[256]; + + if (panicProc != NULL) { + va_start(varg, format); + + (void) (*panicProc)(format, varg); + + va_end(varg); + } else { + va_start(varg, format); + + vsprintf(errorText, format, varg); + + va_end(varg); + + MacPanic(errorText); + } + +} +#pragma ignore_oldstyle reset diff --git a/tcl/mac/tclMacPort.h b/tcl/mac/tclMacPort.h new file mode 100644 index 00000000000..bad2646141b --- /dev/null +++ b/tcl/mac/tclMacPort.h @@ -0,0 +1,268 @@ +/* + * tclMacPort.h -- + * + * This header file handles porting issues that occur because of + * differences between the Mac and Unix. It should be the only + * file that contains #ifdefs to handle different flavors of OS. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#ifndef _MACPORT +#define _MACPORT + +#ifndef _TCL +#include "tcl.h" +#endif + +#include "tclErrno.h" +#include <float.h> + +/* Includes */ +#ifdef THINK_C + /* + * The Symantic C code has not been tested + * and probably will not work. + */ +# include <pascal.h> +# include <posix.h> +# include <string.h> +# include <fcntl.h> +# include <pwd.h> +# include <sys/param.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <unistd.h> +#elif defined(__MWERKS__) +# include <time.h> +# include <unistd.h> +/* + * The following definitions are usually found if fcntl.h. + * However, MetroWerks has screwed that file up a couple of times + * and all we need are the defines. + */ +#define O_RDWR 0x0 /* open the file in read/write mode */ +#define O_RDONLY 0x1 /* open the file in read only mode */ +#define O_WRONLY 0x2 /* open the file in write only mode */ +#define O_APPEND 0x0100 /* open the file in append mode */ +#define O_CREAT 0x0200 /* create the file if it doesn't exist */ +#define O_EXCL 0x0400 /* if the file exists don't create it again */ +#define O_TRUNC 0x0800 /* truncate the file after opening it */ + +/* + * MetroWerks stat.h file is rather weak. The defines + * after the include are needed to fill in the missing + * defines. + */ +# include <stat.h> +# ifndef S_IFIFO +# define S_IFIFO 0x0100 +# endif +# ifndef S_IFBLK +# define S_IFBLK 0x0600 +# endif +# ifndef S_ISLNK +# define S_ISLNK(m) (((m)&(S_IFMT)) == (S_IFLNK)) +# endif +# ifndef S_ISSOCK +# define S_ISSOCK(m) (((m)&(S_IFMT)) == (S_IFSOCK)) +# endif +# ifndef S_IRWXU +# define S_IRWXU 00007 /* read, write, execute: owner */ +# define S_IRUSR 00004 /* read permission: owner */ +# define S_IWUSR 00002 /* write permission: owner */ +# define S_IXUSR 00001 /* execute permission: owner */ +# define S_IRWXG 00007 /* read, write, execute: group */ +# define S_IRGRP 00004 /* read permission: group */ +# define S_IWGRP 00002 /* write permission: group */ +# define S_IXGRP 00001 /* execute permission: group */ +# define S_IRWXO 00007 /* read, write, execute: other */ +# define S_IROTH 00004 /* read permission: other */ +# define S_IWOTH 00002 /* write permission: other */ +# define S_IXOTH 00001 /* execute permission: other */ +# endif + +# define isatty(arg) 1 + +/* + * Defines used by access function. This function is provided + * by Mac Tcl as the function TclpAccess. + */ + +# define F_OK 0 /* test for existence of file */ +# define X_OK 0x01 /* test for execute or search permission */ +# define W_OK 0x02 /* test for write permission */ +# define R_OK 0x04 /* test for read permission */ + +#endif + +/* + * waitpid doesn't work on a Mac - the following makes + * Tcl compile without errors. These would normally + * be defined in sys/wait.h on UNIX systems. + */ + +#define WNOHANG 1 +#define WIFSTOPPED(stat) (1) +#define WIFSIGNALED(stat) (1) +#define WIFEXITED(stat) (1) +#define WIFSTOPSIG(stat) (1) +#define WIFTERMSIG(stat) (1) +#define WIFEXITSTATUS(stat) (1) +#define WEXITSTATUS(stat) (1) +#define WTERMSIG(status) (1) +#define WSTOPSIG(status) (1) + +/* + * Define "NBBY" (number of bits per byte) if it's not already defined. + */ + +#ifndef NBBY +# define NBBY 8 +#endif + +/* + * These functions always return dummy values on Mac. + */ +#ifndef geteuid +# define geteuid() 1 +#endif +#ifndef getpid +# define getpid() -1 +#endif + +#define NO_SYS_ERRLIST +#define WAIT_STATUS_TYPE int + +/* + * Make sure that MAXPATHLEN is defined. + */ + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# else +# define MAXPATHLEN 2048 +# endif +#endif + +/* + * The following functions are declared in tclInt.h but don't do anything + * on Macintosh systems. + */ + +#define TclSetSystemEnv(a,b) + +/* + * Many signals are not supported on the Mac and are thus not defined in + * <signal.h>. They are defined here so that Tcl will compile with less + * modification. + */ + +#ifndef SIGQUIT +#define SIGQUIT 300 +#endif + +#ifndef SIGPIPE +#define SIGPIPE 13 +#endif + +#ifndef SIGHUP +#define SIGHUP 100 +#endif + +extern char **environ; + +/* + * Prototypes needed for compatability + */ + +EXTERN int TclMacCreateEnv _ANSI_ARGS_((void)); +EXTERN int strncasecmp _ANSI_ARGS_((CONST char *s1, + CONST char *s2, size_t n)); + +/* + * The following declarations belong in tclInt.h, but depend on platform + * specific types (e.g. struct tm). + */ + +EXTERN struct tm * TclpGetDate _ANSI_ARGS_((const time_t *tp, + int useGMT)); +EXTERN size_t TclStrftime _ANSI_ARGS_((char *s, size_t maxsize, + const char *format, const struct tm *t)); + +#define tzset() +#define TclpGetPid(pid) ((unsigned long) (pid)) + +/* + * The following defines replace the Macintosh version of the POSIX + * functions "stat" and "access". The various compilier vendors + * don't implement this function well nor consistantly. + */ +#define lstat(path, bufPtr) TclStat(path, bufPtr) + +EXTERN FILE * TclMacFOpenHack _ANSI_ARGS_((const char *path, + const char *mode)); +#define fopen(path, mode) TclMacFOpenHack(path, mode) +EXTERN int TclMacReadlink _ANSI_ARGS_((char *path, char *buf, int size)); +#define readlink(fileName, buffer, size) TclMacReadlink(fileName, buffer, size) +#ifdef TCL_TEST +#define chmod(path, mode) TclMacChmod(path, mode) +EXTERN int TclMacChmod(char *path, int mode); +#endif + +/* + * Defines for Tcl internal commands that aren't really needed on + * the Macintosh. They all act as no-ops. + */ +#define TclCreateCommandChannel(out, in, err, num, pidPtr) NULL +#define TclClosePipeFile(x) + +/* + * These definitions force putenv & company to use the version + * supplied with Tcl. + */ +#ifndef putenv +# define unsetenv TclUnsetEnv +# define putenv Tcl_PutEnv +# define setenv TclSetEnv +void TclSetEnv(CONST char *name, CONST char *value); +int Tcl_PutEnv(CONST char *string); +void TclUnsetEnv(CONST char *name); +#endif + +/* + * The default platform eol translation on Mac is TCL_TRANSLATE_CR: + */ + +#define TCL_PLATFORM_TRANSLATION TCL_TRANSLATE_CR + +/* + * Declare dynamic loading extension macro. + */ + +#define TCL_SHLIB_EXT ".shlb" + +/* + * TclpFinalize is a noop on the Mac. + */ + +#define TclpFinalize() + +/* + * The following define should really be in tclInt.h, but tclInt.h does + * not include tclPort.h, which includes the "struct stat" definition. + */ + +EXTERN int TclpSameFile _ANSI_ARGS_((char *file1, char *file2, + struct stat *sourceStatBufPtr, + struct stat *destStatBufPtr)) ; +EXTERN int TclpStat _ANSI_ARGS_ ((CONST char *path, struct stat *buf)); +EXTERN int TclpAccess _ANSI_ARGS_ ((CONST char *path, int mode)); + +#endif /* _MACPORT */ diff --git a/tcl/mac/tclMacProjects.sea.hqx b/tcl/mac/tclMacProjects.sea.hqx new file mode 100644 index 00000000000..fc875e758ca --- /dev/null +++ b/tcl/mac/tclMacProjects.sea.hqx @@ -0,0 +1,1704 @@ +(This file must be converted with BinHex 4.0) +:#d&bBfKTGQ8ZFf9K!%&38%aKGA0d)!#3!q8B!!"Cp2Kc8dP8)3!&!!$P'(*-BA8 +#G`#3!aChG`d0$%eKBe4ME#"08d`ZZ5q,G!FhpA)!N!J(0rGdP-!!N"3S[!#3"2q +3"%e08(*$9dP&!3#[Pi&PXQjr5J!!!4i!!8q,!*!$D3!!*mhGH6DA!*!'kiF4!!L +bdAJqT3$Ldi`fC,5RjQK+Ai"mIT)55NR(rQ!RhrRB-b[h[pkGBp2c!SVId'RY!kK +haCaq&5hRD[I2dCBfR+[G08GE9QZZGR51GREPZCSq4k[%Fl9EXc9lIc+GAH0VibK +8hm)N!!!'!-@fALIKK'IN@0H46AMN9V)pXQQE-(*dNf14,@(N0YNqj@YA4i[+P&! +VE8XiS4a2m8RI`RHfm8mlVhfQC(YN5lEI5lBIfIENYT&Y!`R[*2YBPdFff8GZ(qX +kb5Dhb6E*mFJQYmmRqmQ@r$l#*EI2A-JqFJXrXLR3i39D"fa32Z%6XkF%J0`Q)dj +3`$YbQj,$A`-fm"VDh%BjXN'1l$I,mlMP*H9RIV%Qk@BCjE%VIER@8AleZTaRpRk +pGTfK3,*`XJICQ3-lqr3@,ff3!&j[HFN@lH+,rR*HZRMN&V)cXT!!@`6!!JJBh`1 +XEjfGYCld2Cjq(q'I4hiK#ePBjbd!2)!m4hk($q)i-XDX-XCEElc5cidhq)3aDcm +2RbZpJSHRGaVL@VMG6j*kR"3V56`A"PR(ej*bBXE6ZGQXpXc58Y[a4dYddUE*hAl +jcCZ-fA%62qAMQK0@Pq1Q(m5[C"JakmeDljl#hXilHh3Q[&h@)[$H2TPQI[*Q9dD +-1Fl[MqH2T4$Mia3N2m$@`LZ,AjY"C6qi)24VBC,fcckIJ)[)LDMNl+aDGlGC+qq +U05drf1R2K#rQAbhrZR5amCcri1TLQS@08Z5RDCJqipr[[2PQ,AST2Bf%R'(iI[6 +DSAid+6ErRIAA2NQN50KFfHJ#Bi[*4TXC@hHm5,X1fIb'fHGe5m-MBj2b'rCk2Hd +&KeS*Q*RDq5LJ-8$U('H,*T[aqcE#q!PM6V2fJifl'RFpe-$c)*lDT[c@iQl+Hm- +aCq2CKPYP((q%1#AfV@C0RY1jYPQFLh#[B9c-kPl()J$EY%hfT3!rKA8Te!2f&"S +lCJda@GZaDT4-KNU"GE5JS%NIC9L#qq5m$#l![r[SQ$VG(2LRB[J958BVjkAl`UZ +(HLbe)*-#`kT([T(RE,k`9l`qN!!5*j)C*02`,)K!%jUU)j8NR+i[2%-8fUYEl#G +P'9J1NjQ`12SUF5QHG6MV+j@4['Ua`C(c*i+Gp4T*lVCUTG6Yf'JRDqdRS*ZUa1, +#'HDf9Aej`HBEEd*8)P(2%EfBdlY8`pRBUr$T!N%1,-8(`jp[pkNk[i,E)e'1Z4A +h30Nq`9i9fd2N5T)q13q5E6)jNYL@ic&%YLBmKXTfDCG!Z!Yi$*IYcDSKYMr&Bk4 +X(m'M4VB[iM&+YZrJ-GVD)UcT'0Q!mV'bJAVMC$Z,8#qfRAK-N!$Y@M`dr#[E,AK +-NZfGH&"2E04JLQarKdHYE0r$BkUJPekD*KXqp#!fqQ#'E#r'BkCX&q-a5lDVm0# +GV@`hib&S'[dk(R0NS`CcCD-'mf4l')rj25"E)"XeVC-0$&JS@b-HLf6EJFGLfAj +1@)%0JBEH`IDAH#b9l@ra@#EEJhK3@b#&a&I)4YiVC52@+YR1`D0H0PM9DYPq#3q +B%VEIaJ1fKHepH-#fX(fH3#UfIm,M&'[V4qaC,a[pZ8%fmJ&$XCf,"cA("Q8r9ED +Em0JSfarJFCTX(m+$2XAf*@%QYZrLFBDep5FQ[N3fH[j-fDJ[@)GY#ajJ$MEb2PZ +fAm9MNfarMJGpJBdDEjB0E#-q8!`ZEj80A+#Hf+J[,"2EGY)'XEdD$m9SYHehm6K +I0[VT!YQqJ-H&XP(ALkaY)2(pCE*49c!+'re$MYM!ljI,GMdHB!JfHZJ5fHl!!ic +&pJpi%"2E[iYh3#AJ!*I*"QDp3MCk6%KE94d2EQb[`q0bfFMePE+"LiL-f"3MGb6 +F9$f*aejVUbBQL6T@6GaTNBhHZ&+fUr(B,a[i*r*10IY'Z&meq`DXaUE8!iFae4P +%$S-+J!fmKYGJ!e2J&pMSJ9I*pKSm`&YXB"lm#p[(m2J&fEk""lfXMUC8emN'2a' +pBc!a%Ic%4Mq6&cEUp9VCi&Ad+EEhi%%IB32,Ej!!$3ai[E80)3Dm36Eib4YP!fp +ZP)fq"6qaN31F"4Ymm8fbI3D2AjEYfhM!+p8C5Z`%!l(4pp3('hJ+rm*'Rlp&0R* +lUfarKXH[bABA(VmZfcrMm6CV'dDmT(qa`DQdMU"XF)2INJfXSHq`8G2INHhGH)# +Cf$k(aqr*"XrlI@XE6Ni,rQ#Merp30MM$(mN'I[faE265RmMf$McH,YZRmB$IBB0 +2`hR9'8%HHDYXF%9k#KXmKrMB`&ri+6E`iPfb8@YUL!hFSPq`hBX([Bm0[JM[8'F +NHr%[C+0AhLXE(!RHK`eF)4GXB0clCD0A2b!EHAj3YNrLFCYXF%6iP$Sej1JIPJd +qI,YXB"JBMJf1m9(C`%h`%"Yim9Hb8IH2brBT21!Df1#&GeVE+'SLIbdEf)pfJ3f +q5Rf``E[q4MEi!,PJ!fr!&QcN5TpL``G-`rBY2$iV'kS8@+(1D(*3F"8E["G1K`h +qGVGXF)alC+1ZF!TXB!(k"MEU#2I%"UHKIl(p*ajIYVBaj%rS!0MJm&q4$@lj9GR +J2(m['cMh0GR!NB1bd4Y`0'ciN!![YKrJFCqeMD8Z!ar"KUE`6GRJ[ImS'jKk[fa +`%[S4'pK+[f#Mjaq3!)fDJJrBISJ('+M111Sii$NfG)TrNBdHJNGJ!iIJb0M!qRq +9$Ib#bf#M0m!kE260[eREH'T!i$NfG!eiR+hMH0*b4LF-6e(AjA5Vd)jhKU3MUmZ +"b%*J!@`)+f!+dB8%393KU""Pb!-%&@)+)3@F)'836`JRB!M"K&K#+#'5%%L))i3 +DdJKCK#4##!!c3!c`!V3JHK!m#"H%$U))5B(33H3!3)JN`!V)!kS313JFa!h#"P' +$S%(-)'5!-J3-iJAKJQK"X##HN!!Vb!L!$+'#,%#@%%dJ-SJNL#m3$83(a"&%%F3 +Ca"H!'M%%-SIiJHL"f!&T4Ga!e%$-J$!JAL"D)&BJiL"8)#iJ6%#L)3)3!)!I`)H +-3F)JAj!!,XJFC!h5"GQ#C%(f)#+3!$G)#'3,NJ@jJS"!TL!+N!!R5"0N#C)%%B) +83BBJ3C!!3`J3a!F#!qQ"l#$))6BM-N-X4*!!eQ)d!M2N%m%'d4Sd4F!&X&&d%$m +3%b"8#,L3!((%&i4ea2*JDJ9K!k%GF4jK'V+*%)hBM&L(i!ba4M4!-%HmJG3M8N! +@%DNJFJMV%"r%@3Ja`Le%#m%Ai4ELLT!!#HSM##1B)DJLc#,)3YJ4B"%N%&83KK! +)%3d39"'p%%-JTBLR#(U)1j!!484V4%l%63JGBLBL+@3F-4-4%r%5%48LLd#(D)N +)J8L*1)NSL9#!#),iL1L)f)M!KVL)1)AB#L&&L%&!&1(`IiiD*UNYp9F`PE&qDh@ +i@#U1e+F52eNdfjVeK@*e[Y@+-4QhB`U[lHHX2M"p&FF$hhV3q19f2`T6'rI9 +XPQme2,DI85j*3cm*k(,*r24dQ%c@BpKf"R'Mi6GV01hbfdllQQNFK66ZmCRC6L5 +c1r#Mb55FUF2CGcZ`f!Va[V5HB)M5LpfF*%fqUB2Ri9ZmQ0VD%V0%Pd@aRlAGX'e +[q0RXj%`d(l)"P*HUkik)'HkC5V1NhTbKUEPKApZRjjX"mpK66`1EDJ[L,jP1kG5 +#DG$YlFj)'Eed@j!!c)&T'+j-d8MU[Grf3'[J0qR@@TpTqK%0IYUH`8Y6f3l%84i +2be9jS'[6X,N,KJ2e"LZQRe,ERFdXNNd00[SP[kTC6l0DehK0fj3+I9@@0[dmRrS +1-03"%4cFYF-0BN%YX@)$LlZ[@E`JM&UQ[,eBHPpT`iEF8aNhq5MV&EPeFlM(i,d +qECL*dZMQc3Gcf$4db&aIAL`HcVPT4pI"kB`qLEdHeUe"p(6)V*06(4lG0S&bk[G +T86cP4qa@E@i+Qr-GibCfD(250QqV0f[aERUR8PUqkF,q9M*h+(GmVMLFZA)arB+ +jUE,pN!$ClaJa5XGJ6A8&j[T@+rKD0lHJb,S69"`6JQUdKYPFSpA2iZJ+RPeP+)A +YHhH'L4b8Xqr3-*pQFD0iBELi1djUU4N)JM"08CB$XkNCpc%MGU"B$E--L04U0J0 +4DmYqUj9E0mcASeTaD#&,r03-KP2c-c0K8V3a(9[NKVM43Nb9TMDV@HLp0T'Y&D5 +i0ic-m&KaY!8BfVd(85J8-p1RKFhFePK255Vf6$'&9"8aU@lahM*Al@'0C+A!*jN +leIDc`TE1+Y8+Q2aX'MDAfB-HEk#2324cXhS1)@eGrY)@S(dd2QZ,aZHf!"fKQ)Z +fH,5Pc9+*YJiCD'[$IeX(pYX%jiZ,)hjcCKlMZXR'@j)AX"B9E%lm$,c)1E2E6%Z +,!l8D@*1DPEiLEbC&Qf`PFT@Pk2Lk+"U0,lULqrL$PZJYrU!P@SSrD)PqiJpDSS[ +iJjES'rkJ*6U(2fL*PZ%2@U+VbD#PeXpNd&*VC$*SUI9''E68qU!-@QV06`BYYCi +RJjCD#j0"5kf&bD#PeVCNd&*VE$*SkI`$YqKD,R1@38ZA2M*Sk9+$NN&,PjU6$&U +kl!-CY(5T(mQJTFXqN!""5jFDN!!-@VV8EQ63dQAYCG$5TBiSJjBZG8-CY(5T$mU +JT8Yp9JBYAHUZ-QMT8Nq938ZAZUN-@VV83'A3dUA1+B1@,[Y$"LeGkS3bD1P5rj0 +"5jFkT!aDZZ`2'E5-%"G%,Bq`*fA3-X)qN8(,#21A3FX)p6FCY)a34j0"b`La33B +Y)p5lC0!b3Ue,"LdMe,9Nd$*#E*!!3FX)G@JCY)a3FjC"b`LeBKQdM,$f-QJCS9i +UJjB4ePJ',528MQA3-X*K%"QdM",[C0!b5Kb438Xpp#U$PP(@83BYpG#V$&T'UHr +*S'@8[5k$PRS!9JBYSp5"CG!b5VeG"LfMe-YPd$*+69X',D28TfA38Jr'bU"PP,U +[$&T'1AJSJjBaiV)-@XD)F6*S'51Qb+#P(TL93FXBHd!',@2-@3BYBp3LCG!b4Je +F"Lhe)+d-@XDSBFZJCBbc!M*S'D2q,i1@-HVi-QJCSai[JjBaeP8',@2X84QdM&' +EPN(,'(9j'E5-XFBbD"PRM@A3-YiEY06$Z6*S'5GZbU"PR"JRJjCaiSMSTA(fX`a +DaYPM-QJCCpePd$,1@XLJCCbaC0!bc[N!'E5-8r1A3FXiHd)',H2%E1(JFFj,b+" +PR$-0-QJCjeb#$&V'LFdbD'PC106)c$F%QM1i3+q!Vf!q234ZJC9JMGkb33(9l@[ +jB!cm$pi(e[MD0pb2ISAh`IIJA(!*Ab1(hp(6F"6`bZMkp$BF$PccYA1`&qi"A`- +2`8H`'Gb'!i#Ti!#mcX`b#%jVM)DMJ-rJ1Y`#6JG'`crm'BHm4Jp[!F2-[!+m"Lb +$,i!Ti'jqVJ"--M-0rM`$H!fRJE["fH"Nm#li'$`-rZ9VrZ!4Q!9@J9&JNjPI%5l +9Hla0&`lh&E'P)BBlMTZ"+E@B`cNHpd$F9ELVF3r#24Mh%0a$F3r$24ch50`eZ*P +1()el$1kaZ-IK(Spl!Zk*Z#IKCYI2&0beZ+ILRSCl1ZiCZ'ILRSAl*0bcFFr"24I +h20ccF5r!ABGl)Hj&Z"Hh9i#SHbRZCELAiek"HbAZ9EMVFDr'[3EhfRc0LDmdr'5 +KKM&B$pCQH%FKkG!YrLjIqhMUpY6&0j,RJZ&+FF[!4F58Ril,T5[kb()89C9dESF +#k65H+Te1hRd,mZYBcC3l[ZJM0HmkfBEb"D4(XNY'iH@,@FH1ALeP!GJ[pIbK6D" +')K'SmlrpB"ZBbI31K-0E$f-jb%i@J-@dPdhf$59cL@U+&bUNRR%A"rVEcm@"([G +hFD$2!e`F+2C!&`IdUR*aS1R9,Jj8IT!!L`2&(qcL)!%-FA%JDd0G(+5%B5i1k$I +Fa8'+'1(L`%K'ZMK)'68Z$P,(D"F(+@5-Li0F-YE&38)Cjq)JTBahFC!!@bDi1%J +b%edFC*Y*,Jlbc@3A"fPRLSZ$r&2ViL!$6A9aN!#+TVNib%R6A4bNTKNZ$P,86"F +(@@U@Li0dGC+,Jl`efm9"!T[MiL#6cA9aN!$3jVNi@QElF4`Nb38Z$Y*HRBZ$Y,I +3a8(k@q6L)!qZHb&1-U!R)ClLiL$eVRGaN!"I0lJi5-)0,Jjbl#iA"lPfYiZ$*(Z +jLi1FqdSA"eRe#KF(qAL2Li1F[YI&3CVHjq,S(9!rMS1FIk@,Jibqhm9"jQjp28i +LRR)h#ckYLR(3ppKiQP3DeB*Lp6TcAh-1Ba460'jPD6XE!3S",TbeVlN6@Q"0Hf6 +8cRE3qY'eH3rBelambp,5SpTePca26YrBcNlhFQjQ-[ba5(mGNKQ$p-FIcGLM2r+ +)2Kq--CSe61@e5Ik)S6pDL*k*9SN1DFB,dIh3p0$Vd1+#G8TQMC+r2ZRiBH$`b#b +5r33dl!1$F)R8'$N8flRHGGi*"@eSeFIMZFG2"&MR,hD`",6#-!aQeQT[M9iF8kI +PR2l4irYk*i9kb,ZpYMD"6+`a++@S5kkKRNiK-IqUIQlJ[(rpUm1c*`9ZHAJ$"8b +@XQMcZI2`UG'U!hFVUDmrX1V4"pYVLjdKc,TL9fI034pJAGeXd9"lmG8N)P1'hTV +[rVJ(i"k)Z`Th0Hj"Z!IM(S*l+1jKZ)IMVX%p#[GSh'0`Mm8p$[Gih"0`6m3p#IG +Nh&0`eq+HLRXDlZQiCq#HLAX@lT0`cmBp"rGFh20`cmHp!(FGlS@i&q&HM(X*lU@ +iPq&HMRX&lT@i9q'ZaldDpaVFDh'r+2mRK)(BS0'aQZURMCeZEJX#8!KFf!6J-[2 +r"6ShfIYLp6QfKi&UD@0r[C&pjX6a@b+GbrQ5ack!kdpdVFRD'1d8h0j#)FXNfVR +q[!6,%GNNQ2!i&F[RpKedf"JhXjkjDEK5l5cJblTjUrGNDer@epr&Ph,EAlUieEU +Nq+a,4hlLQGL6Ph,G-9-2LL@r9Fch9#4A"HC$q*X61eb,&3S+QPjcQ(4M98BqL`E +X0FaQS0C5EZCfa&bPVYPdeV29abYA1CcDXa82Ij0MdM(B$CRd6'q*6''$f3LCG-X +YAFbijXTN&M'QUT(C+"NiGV5j,(&-9GTEYYQ4JhKf*'&f8QDm#L8efcE66QbXc(U +dpk,CBTPd5qqc6,TYLHH6)+ckdb&5DCiqdN,Y93[fHDBJ1lhCddF%lA8QGXHQdBm +0SYiDR+"-'Xl8ET`@V-I*HVfdI'LTDcZf'1F-["M2A!@0FkD)DGF--KR2G+F%MTN +@$lcp,Jrm%QfG,RJ@[YTpNq#6pK83-ciC0Q%mdafBFq`X6'GlGR5jFFm"DBGc9cR +6F"Si&Y[0J'VJTh%ejj3[G3CGM@F'B)eR`,Z-EjTk"BljFKN#PPeVR%2%`$m0LS& +cJ)Z"I`BD!qmd1JE1"L$c[ZQQc3H3!%E)ZalUK3aBGVNR!#ERlN&Qi*C'cF$C!'H +REhHTI2J-r0S40)J3J'M"re#C$C6QIAXD-`@SH9F`YG1[d!9TC!fFdq!D1'Ia0A! +h%*[h,G3`VA4N&FPflKN%b2$2`$[(3B-!@4iDZ'HiD1#GjU0jj`a*bNG)l[TZpbd +f4CDIG[NR59h1AhKUi*IKUS&hQUpf1KmU@)+h"SiGh$8)NH1[K3$&FUGjE0kjYdd +c+pREI6@RlA6XESF-Y`fm-r`fm!iiEZ#IjVPjjqj5&Q"BYZQ(31b(k)CLhlm%aRk +)3h$X"qJ'C0qr!-R'[E[E6Bcf*M(HBCXFJZC-K$aD*5,Ni0Rhl!CShlm!d@Rh8[% +kB0Thl3&U2dJ*UV-K`[)Ai0UiPeZi#l+0GaDddkl&"ZN'EYqr'lTprb*iqa%+m'h +FLi8Y3RJII)I3h3IE2C!!(F*e#DV,-&f%k")mpd*c,bah3A))ab8S$Q'i!-%9q1f +$hMlB,8&Z'@k,8&Z!f4,%K[!D3QX[V*BJY3LRh9$D*dQ&JP5I(08M4S959%Q)+XY +344'U*%(e#P#pmP1Aq"4+6bAK+C5G#U*64A,U%jckj+D5f&5@QST#8d&Q+SP-SF3 +8#NbpmP**A#T+5b9K#GIJ6d3#2%cmHNK(5UFZ"+(pJBiZ`P$+6G2@I'iqmSHjNAr +q[cqb)GTr!b3E)29A)&R2cTm%b9DTV`+%k+N!!ESUJ+IPkcG&@''KRYe,Y0ADLd5 +FF)QfL4-Zd6Ca`LAD*Nki40[%#CGSQcMK%Qd6*ebLEH+%5l40R(#*YSN6,Y%fFF) +PfLC1Z%6Ea!QAD*Xii4*Y%bGFSQhLK%Zd6Ca`1ED*%bl(0R(#jGJQ6VJFfm3*Pf1 +E11&bE"-RA)jYiS6,X8fFF$QfL4-ZacCa`ZAB*Nki(0[%#CGMQcMKFQ`6*eb1EH+ +%bl&0R(!jYSN6,XFfFF,Pf#C1Z"cEa!QABjXiiA*X%bGFMQhLK-Za6Ca`1ED*%bl +(0R%1,mG@F3i[aeCa$Lr(9R%1,mG@F3i[aeCa@-CA@BkYiV#`Vl)F@m9KU9pP1ED ++`q+rbR*X&BIPJ*APf#S1#`3Vbl&9(*B-9TCMUcJX)U`XaeCa@&CB@BkYiV$dZV) +F@m9KkA9P1ECHKfA,A)jp*%i%2a6LrmkZpHA8p*9'"iIkLRf$!q-$H'iD,ShV*jc +X2FFG`N,1D#kplRMZhf&X-mIrNaVZSH8``fDZjQ4R@Z53!*,D2+PqrUTa[lRb`D+ +jl)%ce[%h'bF@GTQhcpBLrNl4RGAaXG24Djm[$fhGe[l6&krr)MFmh[l"UhX+0ca +jD'PKj2SPrAb8lH"deG4HF[6I*VZQS%%bqII#H[d9Z*l*fY)+JrTd2G![F&Pc$Mq +aj0SGh'U1T"pTUffRJ@`N"kSM%NHFrrKGhf-C@c,mR111T0Eq&8V[9aKYCiL4`J2 +q38RK35F(Bh,CeX2"V"eiS"I#&r&!r56TeALV@9EI`hU'"dSCbr-$MbRZ$PF`krp +Z2&$m325$0"iSUr@-%"6`3%mZG1cmY`%G5m61Ic[)(q&CEIjEB-5cIpYVMLAL[`9 +X,,mKmN)#B4*--BRFcdpE9drf4m9Y,-F+0rfT6m5Fa"pCb[`DHrD-YlqTG2c`Mm$ +V@bhpA[,a2*V+)rSpkH,*rUYpjfQUM1LhT8rNXDfNhk)qAYI0eE%A-R`UMke$%q2 +p8l-Q501r'ZqTQMRjTrTYD(ALTqDfp-rVbh'['XdeVXL"Ac)0,`rjqHMZNrl[rF, +amZMp3kSRmZJYb"0jp!UT2mQM*j((mfKHX(RiXVCllYlpEH%pkPf'(d0qrCDI'Hq +admlGqi@ehYl#Jl"rYah2[DU64ql[MCqU"cX)G4jp1R2bKrf,KIAb82Z8,3&jl$a +@fA*Gq3XY2im3L4l2BlpK(fkjqSGj2jkrk9j[r+fGC8(Z$Q`"GVbYhYBl)UqmRcr +Tl-$r[a(R6M2ZhIGZrXkjPdHVQAd&Tle0`di1e)RXXMKpAQPdQFF3Bq0V61E510! +[HIrIS5$ldq3eeR!NMpECD2PAZG6HjJSrDmhQ9`M)Je1(*RQZVJ`+#1-ZbUrf$mC +p5&0lQR%Aml!'-1kccpZbr#0L6CAYbr@`I$$hNJ,k*(1[j('8Z9Ib1-VF(mRM#(- +[jk'Cq`qZaDd`1hS,GQGkc"h5*mcpR%rdQ2X@f$9IpcShNFGMc,e3MfHCHl'`6c( +hFKj(QAXKM@HBHcQ2,(0IGmUrQIZkG8mcpqBCFa5R(55LIj+JNlNIc((DqX'iPfr +LlpGhR+FCpk%m1*B!aUeq[1$NMFAHAa4Ncq!h'00jQlJbYSKiMc'Q5Kj(QFiMH4a +K1Z8m@N`F4$GIC`ThVcHR6(d(EYZphE#2HZI0X&hIC35UdC6$aP%JAY[02mlp,rr +@0lYj+'CK#FMM+,-Se28CCP(1S`q%94lk"b$#'&iHcDRCX1%PCN%5cDR!8pQ"Xd! +"5#"R+3p$i6"RmMKiDAZSYIc,LLR&aQ[BEL6I0cAh5KjR6mdYje'G8kV5e1+3!&` +lfrcHNlbVYVjFqBjM+Tr3cU`!H64%D@Vk!rjHhD%m9#2UBGj3jG@DPa@J(PUVXJ, +Ni@Y9B4k2De@lXNCNAQB%DVArkpjcJ4K!cBjE$M1#flNShF[MFRC4X4k2pTPS",Y +E4rX9MD$)lEdmQ%4h1,"rSZqe4Q!&k&HY%9J"mY")VZ(-q@pGG4lX-cM4(`2,,`k +Zb4P4'YP@"4PURRkcm[`HUeE")9@P22,UG3l*8F'Y!(PSp9UeBZ[42!,e1Xm94I[ +Sc51VVKAc8%cD%T!!Kk+ZPS!mVNL2e82pMN[QcaFcH55i[G*LE!(e8&U-(5!2"@G +K(Q8m5qDK0)*MH548jedQr3qq*ZV4BLM'K2f49Td2p3FDNb8J$k8a(H[AV-CN!mM +M3'VqefI9[4r1l!aB*8E`X(IJkYqE`Td6jT5(VZaiMrliRBFU4ME+cX-pF%dq6a2 +j`$[)fj1-`"+3!)GL"*D!2"55f`,bD&NKS+kl6+[eCKl*Z3q&9-Ibb#,9mhP`k[a +AmH28Ja1*NNXbMdXHk15aEb@!2+Tl[kA8LAMpFp@`kIlE9hePmYaFKCViI[jQKRF +Q2[c5PM)96aFU0M54G94$+d!H@U@c!Z6KUh4K(RQ9,TR(kGb5j+YL2I9!&5[@`mY +$Ue"@J,VU5C86HI!H1MEVYp+iY[JL(aTVNH(ZXeF+3!BE3"i*&DU84hjV3G#*#4A +Urr0!PlB#e09$2"Y!(MlL+4,c11*4fNl%fiQ*Pe)pmY2LAANS4Q8*b%0aB%Y!(SS +$f`,bD&NKS+l-8*j!mJ5A2PD20*FqPNH@5pX!mYLIUf)pc%ZY#G"!hG0RVDrQ)GX +'H[,3f`E8U&1B4hlE3*!!KjjVS2&2i,eQP&D!2$5Mp"Uff"mG@`+#ZZVC4bY!(Kb +YcKkV+a0FCmA0jc82rlMPFQ8&J!e`HMkL[&9Z`XT[H`h#NeHKJQ0MfQY`r&L@pKU +dY)lY%LMA0D8ep*bmbY[C(qa(1(%X)2X4#J$bc(k%dSj+1m"qK2*iK-a(@!%kb"l +!h#d9c03+J*af"D4kp*5A3eT!0"f,bhE)m&J)Y"Kld(E)BmHbY"hbF5fQ+a%EJ4E +6'd2Q2RU1I@[,C(RPPA2b'`bZY0RR53CAbH-SJk[NFC6"2C,(%3CAcZ-ajPA)ieR +Q95c)8mbVR-G4jP9)iaRQ9FRM-HE9PBLPB*GAk35l[,U2(B!4k$bX%VZmbUZmR,H +Y2+%Y0SaHHh46B3)C+MrQSAH#@3(b1'-ZQHl(r9aGB4D@%ie5laBlhKr*h@,(qp8 +HS1RSh@)Rm)1*c3[UV@90jp'kfLFd(BlX+(ZqVMU2[RqZ1Cb(prGpr"0622qE-[k +rSVlKVh6frfbp*r@hGI*hECDm[qqcqIchMV8$$a429hp"C-(lqclEfpr@*IlTU65 +TDcdpUApkkTQYXjLF&ek+rr38!3&6rebK2q[,UHNVM3i1p4Al"JI'"r$F0&`DleY +CT3'X-"KJL6Qf1"5`ija4RXA+a,paH*F"8'@ddH$T#rN%TURq`86GQr'hA*`Yj[M +m[CArcUA1ZG"c15meaqA[E8,B!hTITII9HVITICij-AqIRrrGSC,IaHp#[5p5[Lq +6r@,PZehj[ecjlP$qPqLpdkc1hjFUEV2LAQC1b0q[@,%KA`i4L%UQH*%4dBP)PN5 +X`Pf0QqiBM(X)lU'i'EdBMRX%lT'iDpLKkKk0H`cZXEM(U9FRi*k)Ha,ZbELRi'C +'BLVZDELRijk"HbEZ@EL4R'IMRS0l,QjQcpJM[`"h(Hk&Z0QDZaMh%Y`RifEreM, +Fc$1Y`,d5pbVFr%V9kK8#m)GZLBk0MSiIG)UH2lDY2ErPRM5`Y6TFj$S'rEH(9'I +Mm-J3!VYk4r51kKh61kjh2lhlkce!li&k9qPGVIFJ[3IV286[SAS2dhZihLIV29, +['VfAkMe+lp&kMp&lV0lMp"k[p`5p*qSp5Hr*HNr4ZeE[UAT2dhZkhM2dRURh,,e +2dRZChX[eAU(h5VeAk9f[pfUpeqLp9ZmApGSeFL,qZHqDpSrA!Ad5C,EHFr5HUrF +m[HIV[D#A626iCVLl1MbkEB)i+J(Up&kSpb+p&qZp*2q$60@9r6plcGkPTDIqIaP +rl$,(aU-i@QI1QAmM,Gf6CNHQr[TI3VUVr,+ML6AhN5BV[lf(r@*Ia'-fr(BIpKZ +,H!18Ar42AEpDq296IXC[&[`LbXriZH6#JeS*rLTJf$hlZThXN[-ZXRiANA0l#S$ +MFG[m'GC[d51`H8U#ipf1a`$VYraq&Nlm2S$(9ZZhE!Jf6eP`#Z6qbkhIYGq&c9- +@R!,(%qZXhhANqjl#i"6B&j,IlG3$2+A#+HcUqPhf-'bH%Z%82XBDH(lVYm*'[[L +pTZ[h[Xr"K[k!h`eih'Mp6[deHm,cLd+,F+qaIUYr"`Gd#XU&hM$HqYh)(PGaG(q +IcQEfr&b1*+*EU$2b*mJMS[jJf4Cp4c[Fa+EGNkA%Ja5+,[Jf5M#JNHAcbbQrfHK +MjrmD@43IpS[GbaUSH1"%bQr9pq#lY*(0(-'$YAp'cHFh@LRLTIcFFk#A42C@4Y) +-f9"MrBmG+SF[GA()j(%+kZXL[aC1A+EbM[qa9eI"$XiZi'+LVNNrpi0rkH@4LKG +lXPIAP0p'kM6I8aqNr2T4Rr(kX[&GRi40rc@-Bcjb+f&1qcAXfJB(CHHmq3l%p2# +[i4aU3qq`IQrl%DDeGeQrhq"HeRGD[rASEhH0pIX[%[Yh+Hpc#-UHhrd2`DEk9rI +"hF3Hcqrlh'%26K)2h"aLrDiP[Vl(qTd'rFTGEHZhj$0`i*p)L%IpepKi0a22`%c +mhYl0irf6X)(KkV`BHTIl@qYh0c&'e9R(ibherSmI`+Chk5XGiZ)1lMCF6La8Z-& +T3diaLrF00r+rdm"cmU"Q(SifRNiXqk$m90mi8pD[rMH`h@EpjRmIMj8fM`p49m0 +1hJm68cfrYh`CYJm,4p([2"aYf(m!0QT&2j'6p0-[FREp)pC[hKr`q)601dTqm&( +VGcVqm-K9AcCp'JiIXhi[BIeZYAl[TTC(hF"TaFq%pc4mD3YX(lGq"k#I403hhq! +'K$Z%dp4TK[AlqMpKZe0qF"fTdkA8EF"RH#'Fi"EVpa$c9Vb!dm!qPMl6Iqh`5G8 +EIL*i0Tjk*rL[cN&J9G@PeQrG&A$iP29l!2m9*AM@H#CVICIeqmleA6alp4JF2Uf +m`9h"XlZSNhj'HD1AHRh61%4-qDcmeREUfPLN$[SjqCh@aD(EN!#6m`[VerMh$Ti +er)UB"6G8C`llh-1caJTj&6K*(Q#cj2&$lK5iarUp!AU[i&RMKmLT[U"ic)e,Ael +2H&m8(UKi2TkpPcJ0Ie(R6F"4(mmZqbSFlK9ZS60lH0C`"@[`CG9*e8r`V#&NIm! +0b3rq)(M@6!lb&H%@HV$JfDhXkDqU6X6`m+aa)hXH(--2AL(pY**mpf[@lie2G2# +X)HC#"$J&&&AKV1"Cid@2`3(1Umj0l"r"XeZ)@h"cm&ApRjD2CpIZKZdqblIU`+Y +m2,Z$I8`2JP[AGI(X"R+8EmU2AT%kE3'(FiLhkQ`JMJLHlHFZJ2Z&+aGfmHbf'f' +$$kUm&i224baIGcj+A2Q@p6XCZRpdSr9l,E@("p3Im"!2rjcEVSEY34Y[aBHlH@a +McceNr9C"'IIcH"8A+JKA(IQTEKkhN!$M+IkVH3Np1UL"R4**$NNHDpLApcEm)rY +$h,-'m2$kUASGGbKm@hJ!rRNk4p9kiK#p#Fm!(ppUrAC3d)A,U"6@SjrFNkhIIC` +lJ&XTP`hJCj*IeFAA`30q4*m4@r*lN!!jJ,(Uh-bHp[+VANh1iqd3Gd[S0mQ[fN2 +0R-ITpM-!!!d39'0XBA"`E'9cBh*TF(3ZZ6IeFJ#3#!Fhph4)A`#3%4B!!%*P!*! +%rj!%68e3FN0A588"!+rBraHbERkj!*!&!3fp!*!''6N!!%21!*!''hF1q&l+kA@ +CJmc,V[2AlIADK2)hYqGQ$6`jc5YD9%k2kcABGXEfbYE`c&EZ1QL[(cq[K9YXiBY +N)6YqhX*RjAHmLbrR`-@*REFmAcMjaF[cbmk"NFA)BVSS@FjMC,[`4Drc&Ni@RTd +6J3lJa1H9$6JGj&RQ*CmXI$R#"d8'ZqF,f`+`"CJ!122kBZPd`V+XN!"P"@CE!FZ +a!S%YPMAT12KF%JJ'S'UT6VFi+k+C6$bG#6GQdZFjX9cHei`LcE&%Y+dYi@4MQAK +EERjeHcDA6SE2G$CFN!$1Y'5lh+YL-5HE$6G'FfZlICZMQ9BR&fjbFVPiUVAE[6D +HF-)4H23kRpiH6l5%DpER-Y&Zha2RR4PQPbjf8VfZLq,CD$EV*&FRR%b[IhdmYDl +IdfhhVKBiY[USSj!!FE)0&HX1V,f"'+RH1PEA4T!!@CGAhG*`3eXZRSaIf*22NZM +jdA"$HkkY[EX3bR@J5-ThHD6,,4+0065&)`ik%8fDFK*G!4UMf9Jd-9KGFGpHhXE +'kZfY50IK9U4rIb[5Xl'QYXqY[rK`VFSQq`Zme,P`Hh@EeYE(9qp%1"fM"(-k`!M +@k4M$J+FMP&"2"aL#2Xqre(4HN!#qc[2FqrY-qQ8B$JXK"N"$3[3#ShBYSD-1-!5 +4[[p`#I["8[Z1)UD1-J+E(6&fY-%3J(VqB`dp#+@HH`qHqVkP6LNKU`j3JPI9iZY +fiUX%+3'X4"K"@!Nb$,%5SS5a%Q%)C2d!TDEdSr4eTHrIhhTZi``$E8H-!BKaBr4 +#VIL@X&BL$)&Y)F#13[E$V6L2iUf%'3(FBT!!NABBJP`r`+lf(J4Ghlm(G3[1`be +6`Pf*8!*H"0q*ZSa3JPbkMq!Y)`b$,Ie,5%[h)CKe[8Y0k)ESkd$AZEr$9(F-3kX +AB!")9)"H8+9M#9(T2J5RHHqKX[8$+6e(8C3a4L$8Me!Uqa"iZYilQhB30PhR(Xc +-H`lf43NYkGi"PC*&@+&AI6cEchXA1D[E@eZG6&M$B&q-fZE'%M&H2qp%B&r-!4D +N8i6C353IbG9PYJ2*L[YiYZb%N939U!rN5Gra*(AYM`rA4e1Yl9'!dBk*f"b&*@' +!LC1*-UQq+#H-M&$QKUYD@Z+-$ZJLp+)fr8fbUb`5D,3Z%QD`-1)rAKQfeDkb--T +S64KMX#"dlUY'Reb-Gj0P6IQ*C8eel6ABQD$8@Z9+Ai`h+2'mj3R6JJ54qHY,rYM +eX-)X$YibU-[`[JZQ@CC93ArD6j9kRLE9A-#SlpP'8lHf9dq+U[3"D(Le$@mjE1T +4jNR5(j2!dFj`SLe1*PZjYUpH2AeN&re95[RDUV6lfX,D'qmqH#IMRB*h+Yj*H#[ +`PZ1GL0I@qDM#k65#36U%3Y6JE3@Q&e'N-VXfXIUPAC*k&90PY+KqmFVBZRJ,'GL ++TXCUeA("2)U-5U(,9#p1)4fZ6T!!E@BV,ibhG38mTkia[+4UHC9d9L&2!J*,`R) +VD$!h3*K43M`dJ)TQQH$Lca[`(NS$'i9b'0ih&KcHK2I)JX2KH'F6KmSdC"&M*q* +9X#Y)k[BN8CTj-MCc0K68&NQqA%q%R#MPdrAd)mcGb6cHJXUUH!1b0pk`KMp3V!i +%,0%2)48HYG#`T3L-,P5HEJ5XJb)V9J&rU`MI63Uq0IQ3!-(9)1Dp#+`GTE0G#2( +`a#YU0eXTi[PNMdUKljBeeP@fCC`emI8$r5bDQj!!UarLPq4N`4C9-EIh)4IiMAk +eB"(PXfiV3ElTBp@p(Li44K51d8BBB4r6YJ$+*,'49HiMYRHVbQVECLK6a(B$&%@ +YBIXLP'PLqbFSqiU0,6pGfi)(3pP2E'q&-N0XEi-b8f`NmrZ,l9)SB,R+pM%S"iM +Y5e!1&0[G8&iRlFRq%6`,%4S2%PXY&1C1'cNVqiZfhb*qLHf2"&*TBpmSD)AYai+ +XE$hfJd*Bf,Ka5B#PV3R+Qm@@%TbPlA)AEf'l%FS4B[X@J9PXpd-j5Y[+f$0(Lqe +%+-H)MAeI+EB,S4`VYSp!B4eSq`+8im9f&j3jBRX8bJQ#LHb6Z@)l4H'lYN@K-&r +DIJ2+5@*MImm6fpHJX'GTBdr-elCbiJ)aLMEQaVaSDiCbUYMDSC`QYJp#)Fl3pZG +3&SVYZe!)aE3p"Z9dEDYJ2eH,M6NX%Y[ESG5)l4)Sl((D2J'&8ahD[JlP$,'aCRA +D0S&iY%4Xl!h&)f"VK&)[0Q*H4'c%el2%pLG3'X6f(5L-5GY2SCbYE41*@8[&a[i +K2Y#f!JVlJMEf"$'@YJp!@5ifjXFiY(dEbNUar4X8iL6KQVK*6+#0Z-"HS)heHSI +BeN%j9fc%dAH+lD039SRYUe!`59+f(d%K$T!!!T!!!mL%C"*a+LBfjYFLY[1J1'* +l,j3eB[X$++eL)dl,M'J518&FE!p"BAa5$1)qDd8EmB2EcE5aGNQaXEq*Ul4G$b8 +Y0Q+5d,Tpf0rX@9)9mTb-f)L2-L#Fc&k50HM*R!Db"fQl$XVjB[X+&()VfKk!)Y4 +Y#ZZd3@c%5Z)`EHbIAa2EEd+j5'bIK,*4E(m*KIN1#5T@&Ue-Bj[L[f8"T+Ua%IJ +9f,35FmrAK2RNMFX#9`3Q"+A1VJ5-!&PfbXJXp*9Tf35p5S*&P9re92rNljGXrm@ +*BFFF&Y6GaPZ'Gfqm%r(ZKEFFE`AHbALRiTf#PfbFk-XT!DF$R!T`'X"%1%8J5R) ++)1aI64Q)r'5jC(9Nq@6hc)2-P+b*l*hXJfK)PNkQ3eE1@K*"LC`X2j'5E*%XNHc +3CCYN3f5&C(*%&aIab6L*pQ5$l&fb2l)qXMfb2,)lXMUb1E)iXMHb0V)eXK)L-KN +#8CNS443QqT*jNA'4DC&KNEQ4QC&KN3f3!&'4fC&4N!#p%8r)V-LSb+6)3XLFb*L +)p'4)C%CN4'4#C%"N2Q3mC)*N)'3HC"c%($)-6KXiCA!(GQ6KR%DimcT1%6M0)$[ +La),-L0-%GeV(U3&C'+F%R!j`+N$@abN!f3eC2pNqQ5ba8pK8e`d%UcDHbA)6VMA +TT(,@I%dhX(S5VNZfT61j-1GTNFB9"I2Td83LR8k&ch!5E9CN49M[NkP!+l!iSpD +XD-9UNS@0Td,Sq4ld2VAZdVGTdV2YBH9L#H`E)'!QQYP3'40l3e09KbeMqI0"CPH +AbMQC0G'B3f-%Ui&+EkS2,fe2BDR03@U9LqVVPBZUKrK9q['iHPFER6dh[QTHba( +D%G%L+b38('3'fC@%GP-4rHjL0I-G*%ET)&VelTHeXVUK0Yp'bXrV+TUjLU#D)3l +kFk[9eKElQ$5(04qqal#`#551ELSk`N%j-L,XZJRT1Drb1!C9q`dS@lHc9&2jD`p +Y%hI2@1cF2Vp#,Y*PfU@cdECRRXm,P8bQFqPH&2%#MKHN0eDi+GfHL6P@diCXcNP +LYdlDM@&dC`b-T#[EBQY4e[`'SpMmr8lYi1m1GUfh&TGC1eGALiZU(@ZTA3Yah[T +EcdCPaaTKBAZeHeI9VeaK$l9ckl4cae4!X@0rY,"ZfEeFkEH$ZcKC@*2X@)VXh6( +Vf5McpmGHf"BcQBUl9F*jMMYciTc"h5VK$-,G+Z%-`GdUi3c'h5VKh-6G+Z&Fa0d +UiDc#hAELr%@f5Y6XaCdV[3q[E*9B[iGAYNUXcq19V4,V0UN2K90[f5UarK'[E*9 +Bpq'9V4+,@`fb94*JA@5V*-!FC+XN`&Q,E*8%1&q5VC)!jc5b94,Jh%Df5J+F-XY +@5H"++,*9%[J`&*P*"MiY[82Ec9"NUb4`1a6C+JQ`IV*9%[J"&0NU#@!2eGdU#A$ +$3EC+JUbIE*8%@8IC+JNbVQb9"$RMNDf5)'GRXP85j-a+YNU#R&A*9NQ3!(dT@b9 +"eP@f5S+FG-Y@5I"@+,*9%Z3NAEC+JY`HNDf5)1XR@bA"ak()9NQ)[5GE*5(@3VC ++3Ub0E*@%L"Qb94,LV%ff5N+FLmP@5BJ6C0NU#E(RC+XNa#d#f5S*h34&YNT#h$+ +5VC,3R9"NUb6%,5#C9SHib5"E*5(LK'b9f-3if5UaL3Zb9@+c$f@Va'B2b&D*c4a +NUm6QV&'f5Qc1$Q@Va1EX6lC+E-lkC+[%j[a4YNTXEKR*9SPp,46C+V%r$N@f5Qa +LS@b9f1a,f5Uali!L@bAfhd#4V4,l(LLb9@,r$)TXPA!Xl'k9'#+%Y@20h$PXedb +AH%L-)bmKGf![%[H)DF3lBL%aKeK'E#-QN!!2N!#MX2F,-f&L"c'!q%$1`riPAf" +2XLq)mqa[BJqaL,K!l#'@%3q)NF3)mJ4hcNfH3!j%AN#F*LFJTT(c%2X,-fMf-RZ +BA)(p5`j!A#GA)Zkife(%5hH'5b'1NKm4FmQ0L,IN6Z36K6NmF9@ial1LeS+4Mj` +&a!k"5rQ8j,Fhe*idqQ`m$[YZQKGRC0qBlYXJBhADNd')ifik8kqi+AAaVGZfA5H +EIX8J4MK4qfmGkrfm(J*q$YbiFYZfacP,-VF*Vp"iqjSD&iiQ,Q#9N!"EjZBfiEj +Y&emi#VK`eECYGh''E@U6iVdVL"(Q,T-"3&DYbZDL'F2$&IX!G[(cjp(qVikQKf* +b)i%E"ZlQ9Zm0+rGL!+GRl[D@+8"qH69"hbj3YF*U"bp&"'4Gb1!Q"!01J0@k&a$ +"+)F(i'TBbr+hmG@'ZA&9HLq['&E%#$FM8"+dbXj+Tq4HhB!%)T&'!`"$j#4#hEH +ANbCp-NhIDGXY2%I&'c4QJ$bU'cERVdCY&p`VBQ&ha+K1C-I1UcbGaj,c-QXUmEj +D9kj-4eC8bfErl[jBfV48VJAXcU1jCQ9cjHVqXpQZG0cF'K#9apTFmYAQChqS2-i +EkFDRqeAPX4-lH50X9(LplcAm)!+I%fq6b`flkpU5MXQ0b0epRmPQj)EQlM`SPH$ +d`bMlG&e9(NpG6h`jMqi,9ehb1[-%diP%kZ8'b*!!G0l%kkpmBrdB`Mb6Kc'!S65 +F[Q3AFKD2qr@*SB,Tl-61MX1%1r)i5QlL[*E(5%&Hb'-(!2pR(U0)rQ`Hc[U4JSb +,CUDlqcA26(G@ShL$XP520Tc)-`6NJ30rreYA(P*amaK'SKIbD0["6NIVQXr$#-$ +F94jC4birriqB*aMhdhq&k,amfLICA,XK)!p+*CVNeEVb1&&K-kC2bXd6c0e3`8b +0!-KT9N#UTkAl5'QAQ$pQ1LMY319IR1N-j@%fQ6Sr2iXCl)kACM&MH6`hLaR,ilP +Cc+imRTh&M1CKATM&$06M&l1BSF+q1)XCcH1j@Fa!'Ur-BXEbH(i@djq)L@!@-a* +$lSh[%"iM(mfeqf$j8f,mQ1N-!Y&,-jd5iTX#CMVjUlcQ'F%pLp(I944q6k&I,K+ +Yr!X$3Qd#rMmVM$&Iq$'$(&RTq%51ZN9JIT0)8eh0028aL44qG9#c[Lf4cVamND) +18b''aA4SC*ICB#C"-(hpe5d$$&(Flr$UKN8eKiF2Ae69A!@eXDkk@@lNQe*NJQ8 +I-23$MQFNrl8aBi$XqM51&U-!H0PGphX$Z,`qVGJAaeI+K`E-+B,bBiqR,S@[Ed3 +6q)0(E5DU[rhb[2aUG+,cEhBbbAJUQ[Y&qUU0c6AM6(f3!#p-f&2Ir(RPUhGl%JM +c+28"3hbc)[mPL'!('Fr6I-3KjCJe(SHXli$a1$aH'Xl(i9IrqZ[$!kE(M1I$JkL +9!eqJq(8r$VRAp2&mq*@,+D0IT8!FI[&Ll%X9L-0[B*`d(SFEHe-0bPqd)&["F6P +Sr#r-"!2G8@,L`Ia@rNdU(M1f!B1,"e"$"VSMeF3$IA'PUEf0Dq,cjA1e*M@8m2& +!mi1)NmZN,h!bkl,KTP`de4,0Y-Jhj$E-jfIcm%pUDZPN-TfDhpLq1K'2KHY5X84 +lLf-'J`dI$qcr`!1`##-C'"6aS%c2NqC(X(2$l`"Q`mGAcUdm`E3f8I$aS-+JFGV +%,,HhES`jFf1+qI1Yqp(V!`GqeHPGcHU"M-,IV%d)[ld0B4DME[S8XUC4[Rr068$ +q,8rrHpCSpJFJ-EakBi&eG,qLr&qL8RreaN*&lkIAc#p(-e4hEjFp#)3,A[P[#C@ +q6@%mPEl2AjY9C!mVjPY#Z$44["&I[+fH2lN!,ei'mQkV$mAK[aEf'ir$S`dcaZ2 +`L[2-m6M2h*a((0kF2f,dPMbmH(2qZ2'mH50ql*Bmi["'r0JYHF3KI4Ul*Drq&M& +q5ajaH1Yq6[j@pN!Gm`kQea[a%rIi(fl%llA([A!MIS*hFE#d2@XmPH,&`4dVEkB +5fF1+Z6M)(ILCh5ZIa@paQ`Gq[[h[8[`9'prq-i)p8[K$@djprJNJZ-KC%dmjf9& +kBa`4!m`IfS``rkJca)JT$JA-1$*$cYIZf5F`TqfbCk,!UE"IL[GNqDMP+HU+['@ +G+Kq42%ff4KDSZr)mpU)cU4,pG0'V*FiLp@F'bkS4[eV*Bl(kdi*PRB'2Q&,Uj11 +L5d3r8q)Y&Ee*p'E4PiPH,h@+L2dXbDp"kYBSG6TEe80rSp5'UI#CdZh#e6Z4dUS +-Fc*2G$N,%PVDd0"FF!SYAVT-E5R40V9T3cER*19@DpcKYPT&E9ep$GV*&Ve-p,e +%,aGGa8&(+$Ydecl46c"i)"DG*,X0B4b'$6HY6@"eYcVG`RehP4T#lLdT646GY8m +@IB+I8Q!IT0639"A1TYXcXA`@U(9)G,GJdd5I+Rlf9GFZA"FJXMcjB2P9Pfd5YaR +8bkqkeK,lrU,2XZkqqqk1efe`hHXGd#'IF$A0L(QQ)i`mq'&3i@R'QElhHp9NLj0 +@B$EE1CR0G9iVf2r[q&%$q(AHG3NIcCk(AkVS0`ej@,2K&brk(BTi&C*Ik&l2ldM +ifCYdIJ@rbNqM*[4$IJ@ria#[c%VQe*dC[iZRFDLc#AkF1aAm[X9L*A2CVRJ23TQ ++H+b6llFIIi!c+CP,T)Yqqe+4ZKEUm4Ef`G(*YQbRhdNVd6Ieb9aVCehhCAmFf*C +aePKP%rL$(Xj')3XI44mFTrcXMEcLm(VY9h-NmTLNidhNVcS1dRjRRSL2rYbMr6D +H!SH$TAi(S'cPfQr$!h!i42,Q2CeC1VrE@%bj,EM[19!QkRMRrLYXEa#rmk"-e[% +HrN-fX2Bl#(J3f%[(1r4q1"`QmGC$18AlRI-jf0iSIRrXjE(ahE#p5IZGqP(NFCH +1G`b[+,aCiRfCrDIpCR-N*m[`dlp*80"jE,QF[DMpVN6HGTh%Z`B14dLmak(B%Zp +k!Shim5[3UlAIeLGKNdXK#jRh21ehaqGK3eqVIU+bMrDl(,eP(50eBMhfdhQI`VT +@5RlX,Ge2pKFq"GZafQr"a9"Zer&D2d)JdhkVdHF"kF[h%T11ehkaZ9"Zd2&QmIG +#Fb5rUr*p8$E[2YK1d(jaB%03qQE10q$!q*!!((SPMjFq5C["Sb-ZA[Tq-jH`'33 +[#hkAqRMTqqhl)i@,'Lm,m6lKiGaK934+lEF"pCLm5GIlcqJ!(+0Fqb82jaC%#+$ +DlhG45aIRbRQMLKm6Chpmcm1j-lm+"hj@R(NM$aIRV[NGf14Bq(6q%PrkmZU(B6Y +CiRhA`kF(ELH'DEm%IV6NiP`VHq48LII2HCbc(eS+ffR#NhKr3qG4GNJ0'ell*G' +l,XiGr'hfPX6Mp4eGEr[qRm2'(U%Im8j`lJPZ(-JG,1G22Ca,I4%1FRPN1RQ#aVQ +bi&r"*RHU$L(HDC`VUb+RB8fBhiFpR,[r*l$*2CMpPq9aVQcZ"f&E,(iAH6LArQ[ +BcK$m)(m3hV#F[9HRr9UfjR(1rKalRRJ#53'V"1I+cQ'Gj"VHqrM6*m'jeHq%6Hk +3!"h#AK5F+rppf)J*M(G,(ZIXcF6@Xl6I66Il11GrffNKqmI&1Gr[B1"(RKIkIQf +dZ6MRqbQkG,6JA#(Hp$c1fCrP&q-EY0mYU)IJA0PbrU*+PJkq!V`@R,0rb0c2eRj +I"eki1"Fp(`l%&XLPYq9ac[i1b8L6j%dH)MMhCI)cfFcBr`S2jcE6Khh0IRV)`kF +31GYblAFCHYV&ZAUHdPQKr6D60`ZIHpIlB31I96K%A*FmPP`*fpZdhjBEmMKRhdc +q4ej*2flM#+krp4lBhLlp4hiQrAd4qqBG8PIJNH#FrI&9F$KAH!B[K`[19A$Z4+a +JIV-m2[FCBKYMXYkE2CblILCXFQajjSFm2[F2c%h1)@mK(p)iChqIh%2Zk@cjE3r +RbXL2jDE2jCGiI#j`,B&2qef"qVPmlK[-3BiaELA[e6KRIrq(X-NPp+hNDS,h(q) +'NpcEh2UBKh0r5kiLCh%Hq3cb@pKBRmeCpL1F6mLFC3Ea[M'ClH*R-c"Ab2[jZ$@ +GJ$B9m`cb%(r1F3qfRB)9+QrV8k`Cj`#3!(Z"dk%jfZmka,$NZ[b9l%10IpB0ej% +iDErl`'IF2+iQRXPPUKq$*lKjh)*CJLAcS`H*!C,(9[*TiLcpd&YZ(US[j@VC3mM +9c@-pq40M3Kkqdm[MlmR9C*kej31UhVL*jp9DaIXA+0p,4V[Z&[lX#E5&QXHmCp[ +2,i1$A0rkk4h%@TeIJM-)qDEm)j`0A+2pPY`)Qpc(HK3i(CLUrGj2IXpHJ$`'2UR +cXka,L*FAD,r(f5H5hb6dZX@j#[h)Db@rGH!@eJEYp`6`hFe[lar!3Bk*23RFFI0 +Ep$8iX,mJfiK9NYqGR(q3!0r4MhdJqGe)6Vj4Z3@SZrPP1%rL(-2kI`!!$3j8Bfa +-D@*bBA*TCA-ZZA3(0r9b!*!)"cIhG)rF!*!3+,`!!([+!*!%rj!%68e3FN0A588 +"!+qqEj+bElZd!*!&!NT8!*!'128!!#Hf!*!'dPd'q)ZeF"X)Me#qb,Djk-)A[Li +mb@CGAT5lr9`BBG[)`Qj6lYP(,bAdZXeZHE)F&mr@NhQb@bI,kP%HlBSXXReNRf3 +jRr#&lL2lA)jIPr(,4C2`KC0pV#2l[&`'I0&EMLaNGi6AXHAj`PU14rLLNH9iZQK +TC%pFq'*2XF)VeHUDq6Ze@!5!E,+3!0pNNadC,@5`N!$EV3YClA23p6EXl,aSPf1 +EfGY)ZclDfmQG0HJm-hZhjb#rJI[LqCf$aFY,FcPGH'CqRJdb2kqccXp*[,0hmHb +XmcX[MbbHbdQ@LAPN)C[F3QB,"m!##,M9Ep$PGq,Ca)VYmp`@Lr",XMqbG6-#`"E +m!HI8UpaXeS)JQ"F%i4P"iB*D82LTp`6"Mhi5Qbq'KC!!Tr[90L[aKUMGVMEEa9+ +lZ68ZTaeE5mSAcfXKmVI)l`A"Lpm5"#I0kZp5$DP[Hq!Fh@h[Ck1+CBlLhr@hliZ +5Jp)H!KPAq5L*qTZ6caAkE*Fkpb([jH&dH'cKXRejF58C6S6"c-bXi6-cS6j8rGf +6lpeA"F&2cqKA*Nj`l0($c8C8EMkAUe4`4R"FH&GKCqIGR%`Rq(LiYl"JhMR"1D6 +Ki5"iH$MirlPj*$KZI@PeIkXGEkP129mA6TE0l+F+Rj4&JMGPLI9I+YPqI*k'A#Z +TANPH@QK,e35-0R#L#kX3KkfMTlaDEe)M9SF1NUrkU&@RAf-k%kj9C""jVZ@X)I2 +K*['X`P'&&I01k6DKK0@m*0*)rF#R9rkGc,KmH--PkmUe9A&8LGZPdX#ccE[`*9e +j[0S4cVSN5D2XXEQA`r3APRr@aDr+2ql,[q5(j@-0qGaT,YF,prYc++TGAcJmq81 +5,N$6[Xb3!'5Q*('cS$[ij*,M"1h`MX,TKDrN,AG6UrbKSHVQGY6H[Y5f'4A$eD3 +Fc"QZV$C'"iS$cAUV@S[Eqd`[Ah)"KZI'Jh(M5EZ&eFDfTpfCT8PJ0kG56C[lAFe +Y0[ElD)b5FP3lN!!'X99lQ3h9aRLbchCqG'RdE0!Qhh1QdRDdhfIYC*)fkm@,iqe +[D,BVqfeVbZ8i5BUP+*hBEjLCV0BUcd46YH6LCiX%ZlTU%L9*A0rm4%SahP&"Q,8 +NXrDFJE211P!FVQPVEG5Z(#DJI&8dUY5ENi9GHl)`hT%Xc"TAV(c+TU8D'`H@*hA ++DN0FffHi*0ja)+r,S[CiR"E(iM4p-V&cm9Ba(,9DQ@A9NQd(ZPaJ@1KbJ@R3jBT +fN[DLQ8PaB#GG,M$4A5kARN+Aff'EVH6!0YIP@[)YI5N`,ADj`$EAj3,$A*F,$)- +Z9l6,0BkLmBi+#VTF,Q2j,YGXfPSEdZ9b9H&hZD*GHl,m,PFdSmXefE48Bp$P!X0 +FP`X-LedZecebABlqThBK5bF`EalQXTBlTVQXEA'FbaRk$6jR&h4c,riGdeV41$H +5C)hp@6"VNad'XaC08ee6j[e",'[E-YGPMCX'ZkaPdf5AY5b1GMR$TR%TCpe56XA +TcNYFmhKAX'dV*Rr!m`UN-1(P$-1F&@DmR*dCm[*'VG9C(21bPNecAYDbCG$c@N1 +Z"jVLeeB,UmRq'E0qY$M55U[ekSl(4dRG64C2TLefr%ef+iEhf64%jC'aiR"-6PV +5f"4M[QAj3HB0*FUX8A1B3HI24GNd"dU3!%ef+XE!TLA%"Ir4,(2'KIR3Yf`H@(c +MiPcZQaB'5pmb2eN@$![6@X%k1ki9l,cC,%KiFETXX[A(bm$@c*Hq6D&!IFYmK4B +0@a2M&Dp[Y,pkQaF*cFCYkFc2aJA$X0abdh("cKf2LdBYU5i-b,jPB8,f,CXf0lj +YIR96-'a*9c$4+U-TrB[lNiRD8pd`X-de`m#`f!X$fh`V$#acR6!`$"TKd5jAQ%9 +M5A64V$A2q5EBE*TY'MP6Y`8'*VN1'"J'$E$*VL8PT[d&0XhG,l!Z0VmGYUeT$&T +IdDkp`2c'9c5MlcACl-K`VZX&KVQQ&aJ@HPjJ'V5mSYf12"8DAY[XecEhYFpm1qH +pYPQ[GFi,Clb@qDjeYYXeeqfDkBVcA0XXecV(YFe`6I2ElYQYI@jVRpPDjl9`9QZ +CdjTQY0EjV'df#rFfCVe3&fqH("q2fdAA(A+fFpH9RPlediVQ6*9MLQeM3dfQZ2B +SKK6BHL%9Gr&0)H8@,5eTm[TA,Nqj)A&(S[`aXMP6[R'3!+V#k0QD+j2-FF@KU$% +q'G%e@P%2il-q8%%M@0Z1&%61HX)"$-M%i[)++eIP6A8AqTb+29G#,9%(pXe4"pC +qe)&CDp5j'YZ9l+b$YQaRc3[TcYVYc,GIKfd*pafdC0`hckAFYfZ0h!,j2BRm#[+ +Vb+H3!+13!+14P8L&%ibm!MN514Bj$MNH14`*-m,*b'R)3FLr3Uj"RSXFLPb,2!m +j'$N%13%j%6N*@B@FM*b#""(C(cN9@Bdm#9Q"I!`j!$N3q3hN0j&rKral*%`0kj% +EN!#J@5p!ASMFK,`BZ4Pj2[,cb0FK[i#Ne3,@[34j+I)b*($VEFMA)m(Tr`(b#Z3 +EN9FL!EqZ3ek09"`(N8mKhibm"[N%mUq4Ad*q'3QLZacC&GN0f3RC'GNGf32C%eQ +'r#35!'drC!,j+H3Cb$14DH4VN!#[4B,e6#&l)AXMqb!K@6JHf4GTX1JR)%p%'T! +!p+H4-$!!P[iDmQq3!(q,r$Tb2R)"FL'b!AN+XKkj#,NBfBJ%V[eC*&MUdj!!GFM +2)Fp#RSfFJeb02!GCJm`J6d8Z35j&,N-Z4kj!cN914kj%VN,13-j%2SkFKB3eiA6 +N@j&rK(`EmZh)kj!!Ed(q-I)Gb"Z3!0FLra$j6Z50b$p"+SD1b(Z40b(r$(Nc%Jc +Y,FMh)'p&[KGj'r*kj1h)1j!!ld2HLE`2qAlNrFJ2),FMld8qJ(`3q4$b,Z3pb!m +L&@BfFM2b3mJ2)ap&IJ6jjmLr3(i8q6(Naj&rLI`%mZl$h9G9r!&ZLIhI,,kD8SQ +DGRCZI"+ZrHYiXXTQ$JYDM48JbYcR!IY0K0D"!1dXRrf"Rd#A-41-RHBV#HBGcf* +S0$[9bPT9Xe664ar1p@+,3YAk84Yf!6kiNI)d(hbDhN1)E4B`BATX"9P'!NHZSmJ +BXJ5C3+D43j!!TFM"b*l)l[YTB,a3)L(@E"8)9)q$lj6E%XZ1Q3IeB2dLYiSE!9' +fNSU!"0MT-rJ1Fd'm6*5MG`AP!K,8eV68hp+rSkTY4qilIA@TZ'Ejkc@9ac-f%&1 +p,N@,i9A*4R9`bMUF@,pL24)+3eQK!dile)-#6[bk1@@RA@++Ac3dZ,'mV9S49Q( +P'2`[B8VGr-I*C0$6GReXFI`+f2QCM[(6`YK4+Rk*Cq[QF*8A8X0kUC0f2lTCjJ, +8kk69ci4MQrP-F5(,Kq1dhAa$h0k@&$-)cp,64SYRKSYhh4!ZhV*Xr8KaG09%F(T +a6+r-SP-[$-+e,(m'h0ehkTTJ[,KH#lAphfM0*&hUD8Y,aPb,Q-(U+IV5HMV1UM1 +fGmjb&N[)k0Re9S,2['A0ec3R03k"HSlZeHU29DVq-Srb'2C-6Z1Iah,Ee6Z$lBp +SUSU%CehA&pkq9J[BcfYULK3qI'f`r3YIlJ6*bR1+Xha5UlYXPZQXN3A1mJBY09N +QkZmUHSjcRl1m8#XqeS@Z6dDeRXjbQaCS,"(e0eZVZ9*RZ9AV0eD,VLA,Ymmjbkp +S8FV#8Ap[BVhTR1JXTfTeb"T5Ifr@i[,McR+ePRXX*pfDB4@Xjm0)i@50ND`UpAF +05pDSjI0*,4&CB1V[1Ufcch'@Rp4DPc@TDkN@P-FGl5bRDd&)KHM[0ZAMmRUD'-Q +Jppe1bhFHS!Fd@GCS(6QfRYDdiXjEXX`eI50RUGIT8MIId'UAeE,lXEJIj#blD!R ++`YRpU!E*fSeDql1#GMpmh'"9b'+CHSY5fB`UlTKRHIPk,KSBZ@M&SZ+LLjD[@mk +cFIA!1QdT,#UI0BB*,'hE2)9`flA0krMX!pZqClIi&S*[0i1CTD*,-+!T&&-DY!Y +J(DeAZ)prE4hp`&Il`0Fii"*!mLJ)L3eKFH95ZfP2TP31li*8+qj!NJ4)%9H#`&N +P**IYi!XfbQBck4Sb40SQf$PPSfF0B(aYaJdaP)-c!hF@B)pB5l#!!+I%+JEm'Z- +f1$I`DH$1k+kX)9KRJ'd#e`5Q#D`5Z3,ra+U#IJ#Q#``8q#c`91#j'2["Fl%bB$8 +"$JVX(0JZPM)X2eMMJ+8#8d@(!'m&9JiF&ZXU&KNX&F$%XFaK23l1La)!'dCGJ!P +J08'r!U-(GJj-(GJmF(KJkeKUJ-d$5mED"r`HNa2,"mC+Jp0M,3+1N!!2E"ki0E" +Ei,*BN!#!Vf*""1i3l"Bi-R"Ze$'B16#*,*0B9,(1B&N$2K$F'2JhX'+XUX#J8EN +X3`c@$!`D',-!G`Ifcf$Z`2#"A@64"PD4Y4Pi4A#4B#)0pT'G!)XCF*-'6fP`PQ! +X@ES%q%V`H@!$'3V!")+LB99'Kf2*#kk3!*(&B1h!"`EB3jCP!Hi3$1%Zr#Ai6B1 +p"1m'MJ,-RX(UdEI"j)(RI+@EfS'JA2D3!'r"B3C8jT,SBIXi([EB,mcSDm12QHJ +L$5T+[Ri1)&%!V0J%jMpE&1`L`e"ELkb$*rQChJZb*DGfKMaB0L6I+cjImI[EH&r +eqe)FrU$#acE#S,qBkJd)K-f"391cqG#!528aNl"4i'1$SpH0kQ0lC$#k)0EC0[$ +"Y'$fDB"rf$S32Qqp,&8kYKTX)Y!a$3RZdY''K@d%1MBqJU9c&,IHTbSG@aXf&1K +J4'"6J3jf"cB@k(l#Jmd&U5ChE$R3XA&KXi11lB8q28(Tf#*4&qMB','Y3FIQLBd +01MCEE'l3XC9MJi-1"JIL3JFN5U"68G8G@ae+M(MBmU"M5m1Q"adE26Bqk0KQXIP +"ar6)"JJG'b8f3HMBZJP+*+EFbm3D%m-&'b*drm@$64(P6)fa-8*($'b1d,'CBB1 +%MXdG@b4da-e@#4hE-$C,k0LBX@&#"d#,(+,MDJHf3qKqD2f%eN%IB9Z%MVbc08, +(eSMY%6Vk$ATdE1$B*+&M8mG'#4fE41S8(4Y3YN[SU$Qf61LS1EC0Y#RkQQb,%kT +E2@NV(6PLXiQ1Z0PXSQ26+-cr#@dkfA#LBl2(4K)Gfe@fP1KJ"@&$LHjT(Q`"d9( +Ve$,YN6l#&K%GF3X)1+QiNHMBAV+04-H'Nqd#1VDZ-S8Q"@S6)&a5,"`#I8Xqa%2 +!EVVp#V`YT4iNJ,D8kNdJE#R&,k#eP1T-B'STp6B"TU@dr48S@NUX&!)q5iR94@" +J+H9EJ&qTEr)3U&GDrCGY-cTk%aYKG032ffCdf0$Id,%*&Ta,@YY"CJede"Rj33I +,Kf`0drIcN!!YEPUpMHdf)`Cc!KYSG23eDKNGY8TpS5-fYX2Sb$FEBA6NQqdi1VE +VE*V4`@3M0`k8UXjNpk*('1B4G-`!E)(4dEq%L+P-Y5eERc,PLbdd1ME@E+24XB9 +R)ik1MDN!bm[%T-0@(pfrmf"VVlj1QV9N*p4*X`6EIh6d1rSN1QUEh+)Mem5,MQf +cX'eddVDIV6XkQ%ES1HJ!!i)132HI22#T[XkDrCJld0&Ak)hSk-ZJ$0$4GpKdSk0 +Qf(LMime'("hEGZB)G$#EX29(aia)lD2l%3rQ1r9edD`'@J!GFi-'PLSGI4md!6V +k)fJ(G05r4ZmV(EPQiik11Q0,M`k%!h-%1KKhQ1238Grd!I@9D`E'*cVQ"[Sr1ZU +ERSb1Z%&1S+1[J*C!4he5AqL)RlNB(A'$QN!(q`qc'cTepBK')D!$A-YXTEkZkQH +J%Y!ae`Rl5eI91LJ&G24ZN!!+k1KCS"238EFJ3Y#44j!!)HM!*B%333H6%6d@hAG +iN!!IG!VkUlIhkZZQq4c8#6Vk((-Z1ZBpqL3kjKpQ-A6-!#lf+A+#mJ'V$6Tk!(- +%1R)0b`dkpJ6-0HLB,HK&k1KjX0qJqamHp!VeG9G0i"-GXc'X11MS3mbrk*L4Q%R +4dGm"Qk+Ma`NqUEYU$*BTG0M$%)81GLJBLp$p0`rBLY6A3c-0-`JkCQRQ2R6dH1S +)(I-'GBf1hJ1V%cVb$G3C(A(5Fp"4pmbYk+KlHVlkHQS(!BX@1[S0-`JkqMSeJSj +C"9Jf1RSPc%cSU&9Q3h6%3$p(ada-EYAA5d-Lq`&dl!MS[HLBAGQCS'01BjDh&Aa +fN!#c18TBB2RFTbdZP2R#3lD@Jbr!b(Pm(AlpU@@$TJhe#6bYUjp2@fTV+3#&TT1 +PUZhYXpH`UZdk)+KeDl(i-"Ja0hXI4Z,rKRmXQ'iDfh%jip4H&eH+MSKXLmEr#YB +6NBahH*h6066EX8M@YjbGNA#[6SX[afj6-%GK9YNU6`4TZBErQPUY@Hi[QlCf)QT +dP(-DPhCdG@Y(1lUj!r9+VP5KCTAeM64Z6lE5VX'kA$PRUUYFe%bV@lChe)VDHPf +HKL9adTaXPr2!PME,fcV+CG9kEVQm8Ch+P9ZVYBjbB605QME(ir&8beR9*0XE&Q8 +Q5QB$cP3E9*LVUpd@j394LdE#9LQ[@G94e3pe9!drDbTGR*)AP(1Q@QfRVBY5XmY +@e*a,iiE9`TbTZ$`jkm!V,HTU865Ek(QaH6XP5LC-SflCf$LEY[T'*CiDfEae9U[ ++8&*H2c,lpSUMIU36*QXMSpQ5GL+3!0V4KE611J,Cf'*4"G@EGGSBY42,9q1fF90 +XMT1iIHQXfHECdQTX*Y@TXG3U4#N81ABf%!TF,,fNVBKNXTlZGTkk@f@P@3iZUY) +9UB,DCN[CS'L-DQR,Y6pdPp4)cPB96VBlLUiZ6VDPcGE`Z),,'VYh##L,,@Ql8Bk +5Z&aAiRfkmTCqV2hV3d1+[9FUDP'cS8))b-FMY)Q0lG4G*U)Y+XTL%Kp5T("d$p5 +mrXVH$9YabDa[TNfkZCLX`i(d5,aAdZY-rh0GH[e3p*VP2dXc+3(TFUTC1lDk10! +[8e+cP*kaUU296H@mU"a,f8!eD4HmPN`fZ)9JEHDPBB0T89E2"8TUkqYCXT9*j(A +UXaXEP-j&QNdUh1N&@#UPA$D!9Kd6%(&R"N3f$SbXA$NAJcT3)&$VF!hKMKmZK)d +0T3hle#CJ88V!D2fJ8"08GM[F-$E8V3qdq4Val1Z'KM+Y9)-a@CT@"MUZLl@c"[E +hMB`3@p,*c9PrfPCm21$6-S0#c'SJ8JfLi-NcbRX,k1G0EYhV&Y`-"b@%,bm3E8, +VmNe-+S1UGk0TbV!%+FPC2*D&)fh%YDdiircU*8XUChDMFJf$'T'8N8k6,r(ZTM5 +SeVR9GU)6*-k[-fa&PMViIQ[+#a&9M'@Geq[aIS[2C@aZ0*HcVG[fG9Qr6Mif9j! +!ZP@e&*,R3MHH(H@AV9aQ@CP6eBKbAPBHUQ%8d3#ZJAm@4I'+Lrca&S@E,I+R@Z3 +ZY#JHXe!iU+*`4i9hh8,ZF)VJ6)QQkb6m,*TE*))6*(+A4a3233RZ1fQk6m@rm53 +EMI3X999X(Ll+aY@@Q`qDccc`,cZ`%4jcX(9d[((-kUb,''3rZd%3qBE2(d5Ri39 +L"`a#2rAIr,00h`%'h9`m+KeJB$dqCK5'E8$c-5X23c+#CQX-5@"l#EKePq0&Xe* +`SS%"hXIHXj6HY-IqEI5Yd0j549!FI2P0AGZhE-A'G4RFB&mFh[S[plANp*8ih!m +iVml(jU"C2ZQKCF'mbYjJiH#Q`TkC6iISR4-KP[$)FlGG1F[c84(XZ+`[Q0IA@E" +Jqc,FXMcEDM9Z',#09T0rfa'@bma2S1(02!dEJDPi805'+3H@%c1l`ZKKQ(,-(4j +mS0)0lJViS@(+J@("6&N-`F+8SpN%K#P(SrH&+8HMh`e##,5j-19SG,GKRIm"8TK +b)Q,"-8`jj-X`j@"[Q(*JGa#Q(%G--F+8i`L*,N`jMTJmK#R(%91*-19SYKjKbR( +%h#&-1BlQ-'(+FF45)8`jMYJLK#R(%@Z$-18ibVGd8%FX"X+8iiJe3*Kb(#(cK5R +(%G*GQ()FSGQ&+FF43iN`j6L2m"#Q(1Fa(M)p4P@6`T369Fd)8dj8H45QR+Mm#P0 +1928Y6$P4)E5%+5FUYKGKbSQ+D8@BFU*#dJY66P3X*-+8%pA'3jKbSQ,L%+DFU0J +aK#P(-``*8dj8M!A#P"-9Di-`j86&RL"-19'a'mJJ%&90#&019+KqBFV4M%6#P"2 +p1JpKbY'Y6$$M-I8JBFU*U8F)8dj-G5K-16(P9'DPQ')6TTbB@!5%+5FQ4K"KbSQ +*m8@BFQ,U"m+8%a-VJc$Pa,6m%DDFQ*JeK#NR*YB*BFU*UFm*8dj-Y5j-1E&ri#& +6D%`X8m+8%eGp#e01A,N8TTbiHT-`jF69#i3T*kkk%UDFZ()N6$Pa-H-)8djF[8f +BFZ,+T6$Pa-@d)N`jFE'C#&01A1`K`T36&aZ%-1A%aC3K6$PaeDi`jF6&"#&-1A% +aFmMN'[p((X)Q%"FlK6$PD(BPBFSTd9`K6$NPQJH%+DG%r95BFNV8Tf5Z,P(Y#P0 +1LHT0Q(*+P%GKbLQ4Af(++4',M6$PP)KP4*Kb5M5[#&01L4K2C2G5)TB3BFST%G1 +'M+iPBUb3!#ehbAdmK-@K4(eDQ(*+a+)N6$N*p@CKbNQS[`X%-U&q*a$ZK1TCQ() +5UKpKbNNS6Q(+5DK'K5NRSGfL-18Na+iL6$N*c9A#P*03Ma'QR-6RH!K66N+-4m+ +8Ne4I%DDFT'B5BFV4E%r#P*08Ea@QR+6kPc$P*09hK#NRU9S9TTbNDN#BFT,+K6$ +P*$8R#9018Q`I`T56&11*-18Na6!L6$P*eCB`j56&V#0-1FPrib&-15R0Im+8Np, +F)d`j+G@C-1@N0-m)8dj+mi!`jD686i8T*kAH*%`j+I8#BFT*UDk%+5HPh!P66NU +qK#NR*BBDBFT*LIP&Q(*5ADDFP(U'-1@N2XY$Q(*5$r-3TTc8[r)3TTbdHVm`jD6 +9#i8T*khH,d`jDI9qBFT*Dqi5TTbdFLa-1@RP@*KbdZVG`T569Ym8TTbdqTd`jD6 +9Ai3T*khD%kDFY2)P6$PTq4@Q(+[&"dYUlR0Q"h1VcfH*Z+E',RGkJq`PSk!%"AU +r,UU"Y*[EMK`%mUFI2Fd-mErkG1KZr-[LGKeJrGE[Kqpp!IQ44d&rFdjkj%2jV+Y +I3%0JDIRXFD4(0Y46b3TV@li%,!@+Vm,Z8T`fK0YEc"PAhHGBXi3PSLNA&SEXH*r +U$kc'PKFkR'(jI'LXRHZ(9V,LS"qRk`I8ij,F6ki$"Aj!4+lbmB#+210J2%&q3+Z +GkH-'qEMjN!!I[bj!3QleIN$([2'J(d%-D6mJ%&IkH&Jj[b2hdhBVQ2)$H[*LPBd +kjZ#C3r&BF*J5h#[*)USh,[AJBSm5*%HcUF2K)XHS'5YCLLa$GN*f3CBMZb+l)EX +MHb"l)RXKHb2l))p$(SrXLc`"H5+b(j*VqNp#9L!()!FL6dB13Jj'$N%143j$$NH +13)j%MN+14PBLab$()XFKab-R)#FL*b'VN!#6N913!&14eFKTb1R)'FLCb&R)fFJ +DC!CCLka$cN-Z3#j%0Y"kD$pcN!$eb2R)"FJS8Y%*kr1m5T!!A&H3!%5QN!"TC#Q +b$0N*f4PCMZb+l)EXMZb"l)RXKHb0l)-m$RNmXLrb"15*b(l)rXL6N!!9b!()JFL +6NB13!)143j!!3j($N!$$N513!#14Sj!!Sj'9b$()XFKab2()#FL*b%R)+Z4Nj"6 +N9'3eFKTb1R)'FLCb&R)fXJETRIhKUkac'"Sl,+GfPQM1c[,Ciq!PlXk8[56[e#h +0hrhBc-ae3UER2N-6BhRl$Kr&fIETrC0&i-Sd6$9m`,G3kXJPYfap&f`59pKY'TD +fMe9`X5pF3&qSRCQjbaJi,'hI)X2r"(eK`beETf#Ek[m$EN$,f2GkKJA$HQIk`K[ +F[2#%-I"BfEjdF03*6p'TrqpR(lLYj[NE0kd#3"pc"Bm0K(A&h)ZqLeXD*+Hj0a% +q3(22H[(1pH+GL5fhUVIFb`JPPER[rCIh-TUlj)0l'AIGYQMZ@S46XAM6)TXeFlm +k)&Zi%)8$dEE`c9dpY%+!3RN2Y[F3H`qepc"l$lIh#(Z2Y2FSHiqfGk@papKlV,h +(fAZm[5IBHk+p*pQlbYk6l6h&hP2YA@h[DICH1$LkI[ENhrK,$YdPSRU+q*YZlaR +fRQR[@IDHEHmDHfIXA@[["IDZkmDG1[(J84@UeH&1p6RcMRAGTR888[ES#Y8Dm3% +)f2G41KplIBb&a+$X5qbGk2ST@l$[@![*Yh+LkeS&VZY9kBeGGh[h02[,l+hVAle +ehDVhTIBHdaPRh+YDFeKXZEhDK[DeF[cCAmJLAKh+pAH8"X3+3)9QXk%"X@IFlQ@ +A`jG,Gp"G`3UeDE-)I*CB@NVrY#!lbfH2i`e`cdf$ALPh-YV1ceaiSElGm(%V38a +UmE!0H6Pb("+-hhVN"Z4'j#ENaFM0b#h)VFK,N!"e5&!(Fj(cN!#R)1Z4mj%,N!! +,N3h)4FM&b%ENUFJPb+A)CFMPb"A)PFK9b013!+FMcd#HLA`0mVA)Xj!!Cb0A)mp +"VN'HLeb,2!pj2[)#j$VNkj!!&b+jjRNmFJ*b)R)5XJSj'6N&149CMCb'R)kFLCb +&R)fX3@D3!&&N$$N'14Cj+A))FL5r6HjqEECVllEGr#ef#FTl$#GlUcAHpaUHqME +V'JrEqa&lImMH(lEhlABKbKf'chqIfGeTllXXhZfQ[p[L[FIL[pIL[FrLIlqpllF +,9MjJIKm`[`rDe5S2@EbZYBhLeL#b'mebaTNFd(-k5jH1Zl2hV'e$0fe"FfHb4pI +pB(h8ZkdA&2T"R&R"@HS@mNXR@cVQVYTZCV5T03M6$a6BK$Xj9$r`lSKDQND05Y5 +Zc%*mUl2cqI4,GBhUdZ6Q'ZG8eMI+YFP+h$1XCjKqS2B(*IrC$p4PQ$DcHF(["mR +[c3H+['"pXcGC@r$l3HUlrD$j2%M,@&I`qd(Dl4HTpY9TllB9Spm25PdrU1j`fP5 +mU[rmr[0kVA8'[aq8'6i&l!hi&*NAMUNAm#QfZAf#I3k2)22L)IbDMD`Cj2Z"BVk +cc1%48YRq1HEiil,m#0Df[K"mXQp-pTc$)mMmD*($)b4keI&Pj2T"PX(HRYB4r(l +!C6Yf1Ei-Q4GrL8qaN9(Ur!D[C%IM9dVe`1I,f2H"BeVG[k0UemIEDll69jH+DjD +rIVPNHAIkRKpG'KA(6KXYRKNZhR9$Z(M,X[8MaG&9%m(Ta6'p-SY1[6!)eh+Iqi# +lqdjG%i`AerrJZLYed8aMP*5MQZjNh1PMfBU0krTECEY3[cf1fR-'cMVVU6"DF[T ++(1l(cEdk(jZ$C[QNKjB&mbTlJi@$Q`TlCMiGSRG1C-[TA9Qbl8SlMce@$(CFeKI +-kqXF"BRYbmFpP'HEMmB0!k16ME4DImUrY3LYjal%SdiFhAqRV,+#*c[TrG!P8Yi +2I0TTl`I1S&,["rlZ-Zq(fDQ6p`0LY,2h!cpi&qm(VZpbliICVk[h!`pk0qq(QdK +kH$rF6G,6qq&fJPlH$rHAp2CqZ0'NMrI$(5I(H6rFHR+mpm-p+(fp(fj'1F(liDk +8%ldIENrTjre`RdTrl`H@rC1m(qjFUI"qZ)9PJ2I$[5`$[4pZDMRCqq(ZPN(H$j2 +RB1q(qef'H$rFq$,8qq%1Q'(H$lI#$2GqZ#GQK2I$c6%M[4rZNKRPrA#lc'M[KrY +Q+VdIEU!Cire`*meBliGEDXCj2paE-plli5DE#Gi2GpY-p(kil@D5pm2p0eAH$cI +L62CqZ#0RL[I$V6P6[4rZdDRfIVKCCjVh`edldldIEYqCiIe`(mp-liI&d#c[KcY +lCRXrh1*6Fq4HE'A&[6iC(`mhrG4k2pcp8qIpF"[3(1q(qi(QHMrF'$62qf&[H)V +h`ke#pGl22*l[1Z6(9h"3q%hHMcT42[*4lkH"CrFMIVb%C6(JHlpM'!3rpVKEDQ` +iAbmEjHeap`FGDd&-6KD+TXNPG1Uj#pQG46)T2dT%KMYq4,($$mKZ$`%HqJ(Cl5( +!3cpjC(IS"f5hKa32rB$XpK$JS4q3!0dH!McdNdGfKhj!GRY)mG!2b'i2!4lk!GR +Y)F"$2ebmk5(!3cmJZcd%H1J(C,H(!!rpJ1cf%1#K(j!!h4i#22564hE([Z8RMq` +1rH54hD'I2,)lp*0(GSGqmXMZd%mHf4hkb51l3cpjC(IS*irX$[hNNGfKRcbb1r5 +64hD(I[,)lY"2(YNGqXNMZd-rH@4hk#H2l!lpj*(GSCmmXM[dNdGfKhlbb1l36al +C(IS"fHdKYN-r),Xp"(MS*irX$[hNNGfKRcbb1r564hD(IN"fHiMYd!r)EJm"([V +*)lY$2hPNGqJRMq`1rB$XpT!!f+%IN!$G(J)mp!1bfd1!Khlbb1l36alC(IV*)lY +$2hPNGqJRMqb1ImX2b'i2`4ck!GRY)F"$2b#l234ik!GNYiF!$rf!l2B3i+'I2,) +lp!1bfd1+Khj!GRX)m0!2PqVZ3RCR2q@RRZFZC(IJCcl2AFMZ`!p)m9h)lUE['"! +"f@d,L-+lDb24HqkZ$GB*e[Zl0UaeGk!Hdf$FY@&!Q5jM5JkMUC3f'ZBKqda&@(R +kKi@L+1`e2Ff'q`RIfT!!`G(e*6-TRFqUHAdcED+,VP`pY!+@XK*l*lTqbKC`!dd +@bi4MF6*BEl@E''PkBpIGhMh0rM*l$l(h@(YIDQpBCQE2Imr@,`CAA68c+j2[[QT +k6Tq6jffj1d[0GqqrreNC$Bl2lkT4ZG[`p&deICHA5VTmi-Kh0V+QeDVe"SLMBAK +)[#0l2e@'&D[V3TH&jr%dP)C@pJL)Bq@!H%XX!(PG-MCk--,$FHJVHmkC#-T*XMJ +)(hYVX'"0fR'3!,Sadf@L@UhCE(!dqd@G#iqF+kr1G-lBRQQpG4aVRSqhiqSI,ik +4iR#b!X5a,b'rL'2IB8X[a,%hN!"$FDbGDJ@lEp[Tk[@$$dc-2,SXP,lRIA!ZE,X +T')eh"$r"E*)DV'3eqY3RqV"r10apqkE`LKH*YmQ0BqqY4mrQ!i)q(FHqmRMKqU6 +9QS5X!((mpSSPpmYHRk6Lf01*AVKLUD-qU0LpRijM5c$aA*p@DDK*kN(@U)eTX8q +['KKjBBl"apc[6$,R0XZ64q,Sq*Jr0LIYld`02ipMbd4Yma%rM5Z1C&A&XA@b4d! +FI*88bDYjC9FJ%hI9NSYlPNcF+V0Y2PkFZ--iV!%6pq,AVE%VXm*2RBae)"q@MmQ +p2DX[6Zi(iRKZFMm3ah16ql0ar(abliLMBh,RNmRGHG[#66d!*[HGqAKjFJm6HhK +bliMMZFPpCaJ[6HiGF9J%*[HeirpEppD2LEYUbEB$2PkFZ0[Mi,-%60`VUihccLe +1,ERJi-5Gr[l%T1,BfaQHRjJ1a2(FT20X($qIG$VLH,k6licMT8lH%FIqTUIL8(A +rl'4H@DXHlPEq4Q*hKDJi0QlHqP`FLcG[I5'1p2!K')CmUH(KJ9AIDhrYhmmkAUf +Va'IMZ1Mj@9(&F9'8*',Xf!4)ZjMFE%@I'frT%C!!ek-c3hD@hMq6(iUMSh2ZVl0 +XI@`pA'BrqmUbda4hpjJZfICaGH-1YNVY@kL@cpY#26hGf6jD!H+SV59*3)Y4$D3 +p$YAVb@[NZ6fjhKTD!I+KYhe@J$M-YQp2()HfI4HNp9T`FmR&FGeZV+m,ldAIqaF +AKp0hYB,6`KYERlTjChM0Dp4-e"d"j3hXV`T[V0`66Mq)2iRMGDUL-"m(kdbf,1Y +DhkjA9Lbk%Hf1JpXU)cT"mfMGkbf,&D"HpCE&#K!(Rp[1&"rmlr+Uie$P%IPKI5a +U0Z,$)i-c2&ckVh-VQcki3MY'MmJ*[5H3!!qe)qrmpQl+r8b0lIRd$'-&L'2qe[D +@IZ5VH@8UY9GJ&[Gm,E03mI0QSGrAKcF,rEjH$fkF$kbEeDGRSD2pJ`jmHVAe!J, +RB&lY%jYV2TQTMZ49al(rkYQ$FA4![#)Rphl$H1hj$NdkGL@-ep(2XS6afY3k2*e +fj29jV0M"b6f,&6[k@8#`BMXEb%YBXIC1E3[!LVQ6Hd2F(Mqi&%hfRTMF,4@6U4@ +JFpS91YA"Vi-Sa`+bdl'i-1AXq5`%ZaJl%&21dFqba*4cD"I6&SL0B"HcciF`lZc +pl&Z-1ah`rdKIV`Vmrh-6R(f$4q$jb@YR(#p2AQ&#$NpH(A&B*rJ4$NeHEB(B"AJ +@h$Jd+fEl4a#+[pdG#RElX30-"$S1U`6[J`@0LI0KKp#ZcYJEpCGUI8h29S6r4ql +iF[CHmh-CGXh#Y&mFXf2C(+p692UG6*c+pLRNTX)jEbb'ApbMbb3Y8dbF,q`&BH* +Xlp1Y6*b&2QdTCq+d+8cZGL#8pTl*RFm`-[l6[BYl!%cZGLNQcU0eIdc,`-5jkDY +Vf5UeEk&D2Qm,GAkaXhfd!X6K-h'fap(+1&MBLZQYS48J(hVEC`@))m[%Z6Z13pX +qc6khF$TRiSc1,fa%VjJiSbImr'lZKTldQ61'2PNSlRN!PiU*Xq[RXIp-iG9rmE( +##@r@KF!qqebBMi0ejM0aYXI4bX4CL-0RiMb5$lePX3,8Upkb@!(Li"2@Z#0jeA' +i6*a(iMK'Y'(LG,mS8ThQ'IN4*!`a#@35Q8DQN!#Pb$*N*b36D'GN&f3jNVEB$GN +Gf32C%pN,f4[C"kN`hj%I3rC&RS!m%GN2f4pj%K+8e!$N316*b%()`FJKb+()iFK +Kb"&)ZY-Sj'JNpi&@)X'3!)p$MNGbCpSNj!3N'Cq-R)+FLZ4)jZR)'FLCb&R)'L6 +`4,D9dj!!A%0+2f&E11GrD[ehRkNpkSlk#fU[THiDHKV8(R9(r49VMlUMrS,DSqC +-rEA8([99V$pUMaUNrS+kSmI-3aV'%HU5rN5ISMr4TqU4mj(J-L`!fqrrqq`dYrG +f'KdC@6GRP1VH$HXX,,9M(65qVPD0+kbh9eILYZBc4hHr+[G1eePd2Z#(LR9PS4, +drE$+AKmPUpiaHmIYVBq89Hq%[C2f6YNlE@ppMkakPpQlNldEl0h&hZAflQV[E[E +@H9#0+0V08qci`EJ4YcR&hFf9[Re@qHTKlhRfeVI2URF[HrHfGapl(fI[iqhGepi +Rf2Y%HrHcGhplRf6[#RX2X2G!HjpXld(feMIDUVHq[9DpKpTlQ,f(fhZ%[8dq4pP +lY,dVl6h'h[U'@r8HCqrapTjJliRfRQ6[+RY2Y[F8HdqeGl@pTpPlBEIQiLrTA%% +q+L2i@Cd@AdlpXCda06lGhM2X2G2HXq`pfpiepXlBZpEH#qaGeidlGH,BK%Ed0Fe ++6-"UZE"aXPh19Jbk0DRja,aMAEITifN0jSaldql86'1pZ,hArj,[ISG@-mpp+JU +JkT-+EN*5Yf5hM8V+$pZ3!#V[Kqh,C1m(0T)ThJp!ZkRH$k`AeGi2@-YThJrEZZR +HccUH-l`IYN!c[4mB4@Cj2d``XldId!SehXm'RKR[KlCAkrh3iHZmRiYjc[&qf-[ +2pAlSY21m(jD)ThJrE%RV[Cp,HEkpibHK48MfbcUPUk[R1cTqNPU@Y-Cc1FphjAl +DifC-ZHQ)Rf`LP$PAbHpeFQb)i-I1Fr'q%al&0+QH"lb"5T!!Vfhqp'[23T&RZmc +('P-pParD6cLhG[b`N!"BFG#2dr8$UZ5+JhZ-UlYaJc3j`qmlf&*HkIf!8VR+q`( +*N["l%G!Z5Eqh!4'6mRj!cD5p(j!!0DAH$qLE-Zm(K%iRl`F86fI["k42&qm(0&# +jp`0LU+[h!kUSQrF$kUL(p`-bUDIh!hUTPrF$`UQhp`-+USrh!dVU11m(*0AahJp +SUllH$iLX%l`I8&XRHMmJZ2Tj2k#mqRXr)-&1mRj!Le9i2b$V"RJrLhJ1p(i@mcc +Cq`(Q-FMl1CARB1pR#FmKhXp5RN1p(l"G`lbIj6b(HcmVH)l`IPEb(1RpV1)jb[X +jMHGSlqGdRTAHcaNmahJrCr)FkrfmKZFilqHe2-Gl2fIaR1$p3-N`dIZaT10lP"m +)EVVr(b3""f5eq*L`b*fD[FTkdL#5l(9I,eZaf'P3I(D@calA"e`qV(GR1-9)F(A +'V"8*hTHaE%@#paY3pEdCUe3Nq)"eMGXcaUe)F%h'1"8*VM@l1qepPm@chI4h@cc +h@(ch@Mch@Ac[YrIp'F0A*2KYmrZ!qAd`BrZ+""qaH*rr9"5PDdI@,KpHdEpLid( +5-T!!DQjBkL([jH&dH'cKXUkABcb[U``YmeFCfXqB1-FkK2Em[,'PEXadV3MrQkl +1G-lBRQQp,8claDGZfcR(kq4)RfIL92AV-R%kZcr4Krh$iHlE0i9A[%LAr9LQQ$K +r`HHPQ6MEqh3V%fHK6e[+Q6KY#T1l(3LP[@Gbjj2*hARE`Ndp!#ChZa36jp'k2kC +PB1*FhEL$V9,l&UVPmlC36dphYSp@J$Km*Xlf1&SC"`YE-EdeY!,N3frlV!"aC*N +iGmGaD0ZRfHFH+H9-R-lPiEhS&41R-heA+eJDhV4peFdl`fYHSfDLQ$M,'pKI&Gl +8Z#HFIK"r!IYFQ)q$GHBcFEE(dFV%@BM$Cq)mNJqpCE%#e+[HXPJ"iZ!6eVJMHG9 +aZ%bF4q)i4V4KiS`Fm`h"XYFRkH265HBDlZkm#V[#4V!#Ic(CF4@hrcRRmKL'(9G +rqpq+Yd",)r(&pZCfkri8eJRX9(b"h45ZCBr*6X8Af*9Fak1deBDT)AVVlbYJLC[ +Ve3FjZiGeDDLFMHhm8Zj[`#2Sj1aXjjGjh1MXVKBd8Di4MEk8ahaR0qVA&E"VPlL +CaeKRYrM[C#"fhmrpaEk%cVeb0&,iGbA4YEYbS3V-YBZT!MXlZqfkc0fpKM4bpQG +*JF5a6IfPe1VbD4ipR9hRMk!VXcMSGhfGh52##,YAR%BqrNk1f,l$f9hr3FAUfLA +Tk-jmCpIicaKdX6Li[[diCeIr*qM+cBlH,AAcJ(``pQ"hTAblGJqp%PdhCcH)klk +GSkdZKf,3hHVe34l6RGd8jDq(aI(f2)j2UAp4)qSEr%ELq,DcZdYRR[Db1+EQGA# +(iZ2@9HbiAPrb9+EYEKqcqcFHRh0fN4pAMPflSI39ar*pVbie2plC$9-IZmrCI8H +e4"qJrZKRPCQGm`ek@14NCcIZ6'UmNr2ArCm`)%Ee,D*I*XcZKlq*36rA$Z,8mi5 +hQlrq@##*Mm9(46e0e&rp1fjrr%H8iKhe00[%qhEAIJMeLI@dTYcjGL@k'VmVrZL +l[PhXYr,kq0Dpk%ib1fT4qZ9`m5m)5M(fPlQrDbj(0m$X[T2hbmrV)[@"cQkQDNA +keKTb(hQCXi[IN[I,Y(V'),1MRiQraekPJKFlkNrkj6A#f!ba[)l+qqAM!Ve5qm5 +YkYMdbqmTjQ(QMlU3!$karMI3U6V9GFBi+IhbIX8k`YPpNILkl(4f0k[hbH3liG9 +j[la)[Cim%0qEmhjjSAc4!iR[PVaZUPAhP@C(r8QIqqiDG'1FhF3,mhljePp4VEK +f)c5h5,qm6A-*08)Fe*2%mB6Q@2U5qMBc2dQrG,kZbpNR1,[KUNqTJmIr3!&,(*r +TpS2(d+Nmmc@SrU9IhX*X&l&*F4P-BUCIIRX8JmR1VNTa5,qm3GGe6E(mcHcdbqJ +08*&&e,I9Yj8k-2hb(lk#3E@cZqbpHEpFShPVQK[2TY%A6Ep-d[-Le![I$-h-9eQ +rp%k$GrlI"lVpdV1,A(jRYerkGX19Ap-[I6Y(IF6-Lmb)-bb[2mRljIhb3CkTTbr +Pr[3HFCDcQkaeX26,kkp(4be6*Rkrr23JZKV,KkTMdrpkUjm*FLEeehRrHi0@"l, +RL'Vl*2hebr)Rb*DSHV$%edeV$$Q9Gbhpcr6,1c3Eb3BjqS@m6b39!cNKhp54p-Y +rr"Gd`[Vd6IT"ceh@Aj8ZXYVCV@(q0[ebLZUBANFFc"#QAbj(*rLjiBT(kZB+HRR +%N!",`q92qYbYbJAcRIUZZ$E[Pl5Yb&UEQqPVdLq[&6IaBS[l+hNFCkYAbYVPM4r +1jmX0QQY20Ar-0e)(kj3,HKhji#ejqUaf"%ZGhD@lmRjCU4`XFhCA[6R[Pb[NQjb +6aRr1qf9Ap8TbcVM"[#VpmM(90R1FqUjRjf(kj51rJm%UCrF@,K`crA+FpJG)&I) +'kYMdbbI8NpL(%$il$c0IqLIh[iPql$aJrG+h'ri,QU@NArTfSqLMfAlTfphiHr5 +SFq[Tq12l'kKGSQIA@`RqI$[RHZEDH&!D5P,f*I6@-jcK*&eR*RZM))jEbDXc6Gc +-20jFkhb3!0k@YI2lpcP[9em8Zb#[l!ZNEZl46(LQc6%hj[9ArrISk!Q%4&h%RGd +PkYR8"I9+McR(fAf9R8IN%UX2qV%EAq`8l9f%&Q+%qV%EAqbiGk!6iSDijQq*lfZ +D$f3A@+9pJF4hmeqTX8TmAq[%PjMr'$UjHQf&pJjZI)NpUJq"MFC[l-5Aq"R0N!$ +#fA,e9Chi%Mpj!cT""Ur9r(Hfp3R[-QIReLXSHp-RI,[Yk*aG9QHHAH5eQZp0Rr$ +Y(QC'G-kh[Z,E6G+FimiCQ6Xe%lSlE1H8YhEQM-b[DTk3!(hLrFcKd[mb-qUjlZl +6fFem)rd[Fj[f@-+EF`eab&b5ZIjK$'52ZC`q*(0*l5Y[3bHBQj'DPpaqRhRSAHK +N[eQ9lldb[kEG'AP8hhZS'jP,-Tp6h3XUi1T[GZD5fT-e6l!cB5iN[a)(kiQ)l9[ +[T"r)A*+j8ce!GY8c0#Y,[Vq[2LHVk9'DNpbkchcd(R5bDrm2jNkCmfSAD*p"Me* +IbIXkrD$fYCVjf48`"c$2ZrdJmk"fPTGEAf&qFqI4c'HdCpeQF6rDQ8FcIkCC5f" +2LpJlb[a8frpqG(*ap5MfHDDH(U)@)p,6epR[Z206CNCaA1(XYM0cbra8qc,0a1a +[eAHhqUJl2pAfDhDN(TNrf-ZiG9"lK[S%HalehB-2QCmbZc4RU,P-ckRXBGdj2[2 +RUJPfHZUEUGlTjURfaCTVfI23CPArNcQqpK9rV)BLF91hETePVY'-IUh0(qb'hIM +UEP,2)Qrd&HC20lkk0fRq82-Xhk1DVGhik[j32CJH3MpM2b&er(AeD2EHc$(89Q8 +pH@)1@j29JGMjrA@NjX"5jLVkNZp['[0Rp*JX[XKbl@)rjH+H60h%cR9f%lA,B(G +%(16HlG14,kPhX2p4hiHBUd`Ffp8$Q"29p`ccLBPMYRCEHQKApF%X+h&FS9apeX8 +G[6+2idRYHqM2bLl1AXI%FIhY@$aZGFNF*A&d9[mM2l4kVN#81,CU"rB&CjIqQcb +1(kPf[qMLZ&BcQBlMbTR(Y)YKGU!pXT2V9!Gpkp@-VPIfYPq[4dmJT1pJhR,F2UC +ESYdp[BZkB6Hfc0PpG!mkCR2Q(ZE22h9f3l3rrV,,dphd9kI2fAe#ZAM5fBeRab$ +aG6rU85cBEc"AX8Z3!2Jdf[UVcPpRj8$LqaI0Z*TM,a+H4efDq'lA(-"Fb@M'2#h +jZd'pK$d`m9(6%Ymbc8,-T-V9*1de*Ei'cCI8S,+V)KmQ[KqSCp%EPFQ(U6Z6[j6 +Q''T"Q6a+hC[iIUbFXU2'MVf8a2F0p6"9[pKpK(R!a$G-ZrY[bHcr!3#3"!d,9'0 +X8fKPE'ac,VN(,iYd"cIeFJ#3#!Fhph4RU`#3%%*P!!#br`#3"2q3"%e08(*$9dP +&!3#[[Qr$XQjqU3#3"3+2k!#3"ME&!!$,f`#3"N,c"J$&pZPQ#lY0MNBf)j[X6V) +pr-)@[M$V1PFQcb0XBB4YYT!!BlBbmp[VXI8@DXHp&R)l,Tjk-TAGqXEa(m[I,XC +DMYdQE$PG'+&NaaG1ET%X6pKf)F[aLhAZf&9Nkj&0EXF)f`hi3KEM#peN18,j3ME +K&f(,bA)a[RJ5RSX$Zl`Sd1'9DNh+kqMaK3dfff3P#eR)-l,*M*2C3REijI"pjcI +SE##hYkGC0V"&4eiMYjf[CHr#VR0VNlaFH+a"TjhjRBdk,r-beLfmJGIR`1[bmfh +U,ClAjGQ)E"Fl,hkG9lbhJEhAahY[1A,,HpPj'3I'!JLimTX`E9E8LZjM84jE@(B +,)fcKbr%"h3#-!!5-"ajj$3UZ@eC+c92+@+i5pcbT%VYH91VY,m2Q&dE#`+rrfYb +L[GRb2-Iec*cREVF,3F2@NI*USPV`cU"Dj(IM"j4kKqLpHeK$mXi066fe@b8Qh)[ +a9Ep$aB9M-hri)ET8k$J-Xr[K)i%INIlhN!$A@L[TeLm9lVjKU[9(r`mhKSd&L3m +f[D6(`mf&lHFZ5%SqTD%9[Zm2dmklI``Z-II8pIUA4k(X-mC262`UbPAIB+CVrX[ +,!bq*NUf2#8!18kd1JGV,S*!!M$""eB6PSbANc8YS@kX[qeYEI3Qei,U-@l8+lU8 +-Cp9bp42'UF6HaRqj"",U9iar0%ER,9FVN!#'kj6ked`X4Ch'Q+h*b@(2b!R$qS& +FEl,QfGZFAImC$hm*Y8J`S#QcqDlq3VR(YSUfPmZPjRa'VeDTYVa6UC9Y%kjEPm2 +YGY-ZcR(m8QFZPeCUiA'!`%pd'%HGM!QSLK89X`pHS'f#h%&`CL1TQiM6Z"f`CjR +a+H2Ca(HEf"09qLD6C#9(V(,F,+K&*`"L18"HRR4S43MeN!!+l!)+UXRP82p8FA) ++fhYI$eA[iVe2iV[2f3aTLmSP)-bA3EiGD4Cff$LZpp56Sh[Rrpcf'TK2!LaU0$b +0K15r'GE0Pf[45*F!)IA8@b%RkaVcEN4N,c3(XKIihT*Di3mePC%LQ5CGT0D-iaI +8P1Q46V8[CDEF5XdTfpkdmEQV9X,d4,[EVTkhR1e8GlcUdMDY#NQBlaHXmPbSDLe +pVd91GFLIYYkrk!6ZM-ZCER8QaPR@6QXZI+f"rAF&RM86Ae[G$pb+qAjlpcfZ9jb +ahUp3X(hIc&P"DFCdHYdT&eq,DIG9ljmY(LaR1,lPqhCPm*95`,a5@aKf*,GY4HV +GljiV,GqiXl,D0Q339V&@jK)5fP1(C@rU-+qN$X1jA4XZ@(98kpa8Tep"b5fbbp1 +QKpTljM*pQ180fB'CYi2JI+TR`Q0VaUV93YZPT6Q!M#d,q"ME4["B-Y3b+0RCI1I +M,f"MZh&3`,'a)Q0XBi!aYSKaXC*jKE$BYSL+XA%-LV&PM)QaC35**F-B9NV@lH8 +8i@%qFANiA'lE88`+K[N##E'`C0L6Xa!*5hB!BGQS8Td4$-D@-3V'PN83c%0$M)& +L-DpNPkIa,l!UB&pJ&q&HE+3*MfeXAR1a&V#ZD"L8CQ#S'"IS$Ei&fKME5KP9'!V +XLTJ@'-Ci&PM&@"CB46J@'m9i%&X@5b2#VPabmVK9X#XAKZ*9,ZNK9X9'(AN*-5U +f!Crb"UAULR!TX)Sa+E!UiP'ZM'-XfRh9MMP#PMFYS&,H1)+QXU8QZfaS%ea)3J' +T+YC"9HDY&E2b4JDimLBaHRA8J+*2hVL)BhRV'-cbTM'Lj8dM@#YEaKK50Ur89S4 +bKIcPSDlGZ,1b&23+Y4)LApQb0h8K"TB0!F,P9Kh9'N&LhM6'aEaT%4`,H"%MT-8 +$BcAEm@Gk*JIeQGPDi&5F2HFD)`CP$UN(063KPeXHNCQfQQ-9XRNcBb-jlH3cMV6 +FD0"!biB5Cfa8$M-J"h')HDkRiH@0*,6!S"a@[UcLf*E$X3Dih&+Lc&Z93rAKU4H +T5`jLX#jC&a'Lj#!2%LAc'1p,eK(N9iaMk+ai825Xf&US,"G&([ill3-'8,Eh58$ +*,LlYNR98h4h'h3Qcj9mbI"!"LMbbbd&2ZL0k8c&qS%a$NP1aKHGd'(D93maf5YB +ai5PC&`Kmb6lLm"AMVP3@fNh6Zep[q9M6BY2(fLh[@9M68JI0'KCE4GDZd#b+c)T +YQFJfejL*V0V6@fSDj5fMYP&J'651V%@4EeQl!Z1+cCBR)q4LeU4#mUcamQCJ`E3 +pIB8q9f6@99,jAPGNTGfZf+5Ff',(bpVe6L2-X!IV3d1fCfSd+9M2l-qp1Yi!L1f +rUf#M'"GA#8dPpPf-+cE1K9@D2#K&&4$QBN5"B5kDZ+GELL42LS[Kj+ec-CAlF+A +!5KKIM+e-Ki2`+[bJ&''qqRBadeCeU'i"UcUR5+D%b0-+k$R*XaK)`AlUh+60VQC +R%8e"m5LS"YC+$[*&fKjml+!FHf`HKKlE95)2#VSpkX#i((&J'NBEf&3LcCGjHlK +j$q@BmrCKi(R$5[3P&'K23-P*13FP&f%D5VD96$J@APeVk,$r-A(XccSRpQij0qZ +j2Tj!f6lF+(%k"kV!I6#0QTri$hFb5-KE3'BJ0i*XJ4`(@3rC"$N5FL,NPT!!Nm* +4cRM39`C!%j!!5FJ8j&M)@XJdC!Db#E)4XJ&b&()`Y-bLkaehPapBiFmCEq#&30i +F+r`jS@ZA%r4Be5,Tm-02TVh(6NcH,bhG![)8$pq,hHciKmPl5G-fN!#YN!!Cb%Q +3!&Y"ELef@d#f30C$6Y"6[(%J$S48Z9$H%b$P%-#$H'Bm$6LQkG9@CYYTDA+28jY +aH(4[cYcBH8FR9'k'V4DjUIHFc4TH*-M@0q8Hm4kK8*Y#MSGXKC`)kqp#hJRj2FM +[3ji-Z3$b&-K6)9m!q8*)GL1G"VN)mL6),d21Kc`(mRM)[i0m1H3bb"-J,i9N3[m +ib1G$XVU!fMXG%Zi"i3BS6Q3*LM`,mQc)Bb#I"hNcj,'3!(m#q32)Zb$[K[`Kj+X +KVi#m%[)eN!#A3li+mV@3!+q$P'e%hUp$hJ,j5XL[3,iHmJf3!'q%I"-N62*Ub$G +$[JAbcb(I#[Nf50Nbi(dFmKf3!+q!I#INZb#r#RN0j,XKEi@m$I*VN!"r,pM"hSp +4N!#M)9P4`$U!-C!!Bb(C4Y!!qAR)6&LcE'c`e#R)mb"I"#PG!BqFqha)0Q`)HTR +p)H-J0iCNaG+QN!$M#8pDhCY"ELl)!Q*mJCJL%Ri$F0d1qAA)Ed"q%h*lb"dJGi5 +8I36HcC!!ci$F'A)Ab#Q3!$#lQb#hKR`Qj$D3!(m0H3(N5b#hKE`3NRl!*-LY)+G +#lJUj'q6ZN!"l31i*#5ke31i&13eb!Q3Vj"FK`CmY)6m)q5()$d0H#rPHb!p!bUi +%laMN4b$I!rPqb)p#AJpj!q6()$m1+Bc61`(j5FJE)6m&q@R)cd"q&K)@qMl)1b# +r"IPYb1p!cS,mBdKKV9i'mN$)Jb!2KM`%FLlN!C!!I`-*Q$mAFJENhd*H"(Naj2k +3!#q$[!35$M%GmMQ3!)G#cS-m$2*`b#-JCd,Z$ANNj&'3!-q#I$ENPb$KX'E0K(2 +KfGaCl+DqE@f6Br*)cC)rDS)k!5I!"2!$,N$1`3rb6Hl*,6NQlq!hq!p()2p`#(! +5c!$(`!GiJH8%e"4e"1q!Fm!Ai!j`"I!6RJ"'J8&J)*J*6i'[J%p`&c#&'UDZUA& +`'#bMeX")X)SkT+BY6S&0e,DY3fS6c!,r`$8`%9`&hm"-m"lX"CI"Gc!26)A$`)R +!AS[VB#ki$Tl``!3`'`i!YS1rB$jF!D`%Km!GX"8H!HF#-q&*CTq3!1)pH!5'`MR +J"Q!4q!9rJRI"cq"3F#a`$Ni"0i0$`22J+A!4Z!Qm#li"*i'l`IRJ#Dk$4l4ZQZH +-fDr8er+eM4lEhY@@,cp"blA5Q''dU[6+a4DhI2hfL5ZKQe6Sq`$+p@3HTQ%M2lD +PRI2mC+5Vqdc`!MB`#9ma'jM3,pBP*ac1Mq2db9E@S'lYcXI(3#&HZfJHkGZ10%T +Nmc4Sj*GkPE)@"Z)@@"d&iPYHak1DU(V+hmV-Upe#,@DR`*B`9madLM`D$T0CQ5+ +hZ3*1b6FEl+`QqEIT&'UXM00X#AZhm6RMqiNA*VG#PQ[6!I3F%6-+DjKNrdj"H42 +KSFqq"PbLaliE9T6U!'EdZ!`HQHBQh,T@--jf13f&iG(fSc89'0KqCf!3aJahY3b +8(TDr1pM[YeRmSlGQAG0$J`[ak#[kl02[rpPZ%[dhqS)mHTZf9mkm!*!!cQ-'`*! +!@(Rdp1N4mZJ[@cj0,jPH*Bpj"rTX2(V)p-ji2iDNEmIl0dMUK2554rTUk1MadEG +$4bm926VkI[32dG&A*2rSk*25Sd0(rj8q+6TkS[3FdG%(TBH)MTiT[84dc$I33d4 +($jeidG%6TiH)MPc50d6(c!Ip8R6hiSHH)lTIi)FH,H9*,ZQISL-Ap(l4d8HP$iZ +1hU-L6**SS00K5IBJkCHLSip*cY(4lk3[LJlQTVh'*(X+p&(48D2dQ0(4rkI(M)j +FdS0'ap`*r@"dc,'3!"0dpq'([LfkAq1(qJ*H`!M`""ee52mA(IQP$ic1pRl4d5Z +L0ib1ALVpB(6dR1NKSk2[M%pdB!apCR6-M5KV66&RfYp2FBl%ah9[(QGDk*qMqeI +mk0"3$A'0IM3kkTEk3NHq`!Gdp(VT%k-$KHNES`-RX%9(rjNH1$TQBV6h9F0C'2V +Mk-!rq[VSQ+fJGik113GkrZLB(G(qG3ea!KU!lTHN!lk1)8M$"Y#"PBB9L!jXJK5 +J!`ZB*8"(AF%0d*&(`a&%4jrE%!M4dBG'Mikj"2V8k*MhSFq0MPa5QqM!#@B1d)( +Ip-(4-Dm$Ad!(CS0,PNS`Qi!1(+FrMSimd6G(4jdcji#12&&Ik-J&q8%((Y"64dG +RJMN%G1!Ch!3GH)BIG-`6-Gq!MPN@jL,3-@F$6U*MYNLE1@RLK(,%02E5QlN)G,r +"MmjbCXKKG*!!180ZS#3h3mb&,k!$"jP43!IZ`*A38I2D--b`CTKG3%IZ`""da+P +GP`cR4-"`G-a)JB(Sb$'fk*MAS@E4-DZM(CS-jfQB,8&(MZ%[k*K"Bai&(68+Gd2 +h(rKK2N9HP[b0149di$IeKBiFqaYKh6F[#4`2eZ@lAPjcHbUl[U[GE&rIfGq*hlQ +pUAiKY+k84l2AhrdL5f6@Ka"dHQN(eDa2GfmTl(#+h0&bC"iMX!pMc*UH@$be80M +$VEN9&c$RA0F+KHdMGA5Ya'A,H&qVf,9LjKq'f"Z2Q0D1Zqr[JMdqXp(6Ua0E54r +pQ`3LT+Hc1[1P6)Pb3TK,lE!PP'19XApS(8EmBB1"GeH!K38,UPE`4[JZQcNc9mb +NKi[LqJEA4%Bd+mi$TVKf'Sff00h`TeL10C!!@1LDQqN$fSHQE9JV48MId23,V@X +DKUC4L!&XG!ehK9(0[q$$0I0Xa5#E-REJZIIBhJlIC+'ejTIfQ6FC(8FI-6UfV4h +)QRdp*A@MQHIIp2BEeLKM%cB#62amf`dEp48'LQ%'1#Em[6#)j%KLc@22U!-($Sc +,a*Q(YkTRRhef8Pk&HF0)rrl$KmrjH%5fjl6LUaLlqd(Hq*R2a#6%DCml)ZrJ3jH +YN`!IIKHlBkkADiIGad`-ZqXUdI`VBd8IHVLIf'TV$rIJS2(Ck[eb1iVm'lBDkJ0 +Ce&IYqZdVE,8DFlXqHRejU1DU+jDUA6'ZeA'XcGqkq,Vb2qL[2HiZ2f[VCRA09Z1 +`A)2HRHhf-APT*hpTipR*AkEPE010#6!lqFYNTChmCD1@lHqcjFT1r[i88[ZIM'l +BbGm%*epemMI"6V01rLBiCDK6'@Dk9LGr%p`dTT1r#@kSdXRI"+IBG2)h`HeNG[, +h[r#MNlp*jPJRIj1FAYA*hb3R-RAb0mP*3Chm6A)56LGrcI5V6[iQZ3&,*hq6h'U +QNlp*l&QdNlmTeSC1rUBirD'6[bP1,qVNEiVp6Thm6A'#8#Gr8j`-eFRI&,HHkH4 +[LYZcG2)ha8ea1[QEiJ5LNZXDeV"1rYB`,cVj@m1DeXRI'MCIGI+hKK101[PV*P* +emVH'FH[NE`dhGZRNE`fhGZRNEmhAmD16[l@X@jhmV@A-1[PEbdPFRIbYjD5T6[l +@XQZYNlqeh!URNlqeh25QNlqeh##QNlqeh*bQNlqe,q"(*hr6a"'Gr%fc(R6bedb +*kZ4[QTeRR@*)XljemMI0+8QGr%ec@je1rUBjUDf6[fPZAY2*hc4cS*1rCJ*8*hr +6[%T-*hmca#DGr-f`$R6b0m2iGI)h`rV6,S#Ck06*h``R,(AbedaZkZ4[KY[BG2) +h`iP8RIc0X#qRNlqCV`P@!lpJJ%lqCSN"1[QECGhUj'q@HGI*hbcrGI)hbbPBRIc +0%[pdmMI,cA`kqCXPrZRNEjEiTj1rfHIaSj1rGF4iRIbY)bEUj'mGke`RIqZB#jh +mVH1NV%lqeR&b9#GrkaLh6[l@F@T94ilUZ192*hrVL%-kq9Y2,U'6[rA%4*hmV@H +qGI+hR[(Vj'mp*fYemVHHf`TemVHH,&mRIqZCBjhmV@p1rYCr$cmkqGY!2U#6[`h +%1jhmE@$0ki4'!r1SNlm0R-69bGm'EMl8b9rfYGV*h`ELM8lq0Tc&Mdlq0T+ck44 +1)q2@bGp'BV&1rMB5&h6bYj'jeXRI4Q+-pYXEZEG(*hmE'EG1rMCb3k01rMCb`k0 +1rMCq!cmkq6Z#I%`RIdH3!&ISj1m)BU91rSiJAX!Td*%l0N1LSkDC8NE(TN$`(af +E!*NN4mH8-CXPjBdN0i2hSD1fQ1T("fkaF4)GY3H'Sm-AYBb1$BCXS%6(T$qE*p( +GJamQeH8eNG1bU3%G(!cX43G2S%E3JB28+6VUM4bLBe-P'b[4XH%326Sf'V,"%Ke +6i@bbP$H+I*+0PZMJ5r!GG1!lR!XG'!5R4NHG-*f1$Pl#GJ"dE!0Jmb8klRaMdPi +HTcDCMBhS`(if,D+METJ!4dHXa))1V'6M)cT`!Dk"MRU!Ak0M#`GeK)lYL0S"'me +0S'b%42FVr-!,j)dKhmBR1MJC@`c3`FHBVNF(pS2Rk-JhQb24N31`#"fe"$kJ``B +qL!lXCjX!1MJ(facNM5ARCY-N1R*22D+$Zm+GdF(CG!rb@2)6qKAS`'+fIk)$,pM +FLBlDJ!1L!crJAqMBD-(@6(R0l*A3Id!(2kFfdGPYNZM!BcCpS)0,X#N$(AM(GP9 +de$CF!adaJ-2Sk*d3Zlb0b+[BRS!1h'AV+6Si*02lk1!ce!Jkm"SHJ3jFB2XS1[* +#I`BGI3Xf5XJc,4%i2MU`N!$Y+ZMJVq!c1R!GI%-(4k)AJ3iFeeQ4FF310JLJ)ip +X0N%(lS)EmMCQ2iKH$6TU&'k&$Ki,ld"(I1#EJqM&"aH`-E`e2%[!blLia@CafF) +&%-GlPVGEC6D,DS%GALpJ$9ZaZEbecrEGZSI$-,`JA'QqAkh@@h@#C)(k`faI0'+ +C8k)jC(!lP!ZG`3V#hE(S0R4HEaA6a5q`#ME#p2+pfB%Y55a[hT(D16Jp`r`IQHU +V9h'!a8QKfAkEmVeQUZ&&8V3$G4+3!)CZNl6Me!%ZAUi3(L1a`#kDHMiMGje"P'J +[5R)dc,&MV9#LQcf6(8Qr9"i29LTJS9-)lb#3!(K&'k9UcQE4STcVfDmN`-EDZXc +%`3EQXJ*[BYZlc)pX0hA+J8N9IX4P,P08JEJa9@#ZbM!D[jiNKU"`CPV,EhG@V#V +HP'UBDCQ*ZV9KE3bRR6[i6UaDHbXee`Y-k,ee9VRXZP8NFf'0Z1%IeB'P+29'$E4 +b#)DRMm6`YU5b'cC-a#!RCN6U1'DlkRjD!kjD8Aej(kC!`Vb)hX[N0NqUE@C8UCP +"'dC[M1HPJ5a"*4`hAJQq(5Jc)jd1YIR+bYPEE3(*a+))$@+LqJ,HGB5[[U+)3rH +NTAKTJkd68bDqmAE6QI#YJS!LZ-UG35)PThi-l&'-2QBZ$kqpH[d3b*,a$cF3FiP +haa1d$FL'U(1f2KK!@N*rIT%%,XJ2Pjp%,N!p[&SX*eX"23"(Bd1,Q(JdD6@`9dJ +B%4D0L`84N!!#Je3q*KIa18qVr("%)HHb,,6pJZI8!Pd3S8%ZjXL,$65d-INiGFC +6P+@mTDQZNNQ"em$XTRH$Yq9Z1SQaGhUhCJ8Ri)+,a@8lk3Ie3CqFle6i2c$GYG1 +Z"U(H"(4)[K-kSk#ilF8Ti3NXUSX1jbQF*j1r5DC`L%cqqTMij*M5C55&%h[bam$ +NcL5*,hq*,QXTRG-5CY3HcK*Gba+Ib",IaG*qN!"4G'T8kELSm*bSiK&"hSAl2FV +RHS6AH951m9$1,V'GX-iYlYEFhIhpp2l)M"a6@eKM,9VYc[Z(5XK3d8Srp+EV@Rk +BYkK[qD'Iha$jX51**6r-(ME1q['DITK&(0Q+KaQLdE0q,QVk-4G&2*K(jKLI14G +hU'#QCZq@(fBHRphb``cNl-K2ac)A[Fdb2CI(8-(Xj'9ArH3+`!bFPUG30eJ4r*M +RLVRdSDm#)*[f'KRQc(+q(&#E5A6dd@iQiHR'Kf!cbC2E4b+pIB1E5BVqUh'[bdq +8Mbkr$m8p[*eP4Gfi!qT[R4rhJHbV`ICDlF33Ebc@l0)'*m6pA3k!Plff&G1YmU4 +h-Q*U,V2dbKH"Xi[&`A!4H2'jRcSA%fq6M6$K6V@qcS8EJ$dAp#Xk&i2GKZ,r1[E +FVZq+HcMIrjF2pd(FkrDCpG9NPlrU,VCL21f[&)GEEJ1SZ6a(pRrPjUALYPD`09h +mX&IZ`*BIGMd0c(2TlZaM@hl-*50GFh1K(hE8lGIb`rcDISejLmkj0,&L4pUc@Rk +BAfZ)$r$MMVRL5SBJ4jhEPl(Q4HFPZJ'D0bZ2$$#Y)VI!!`JDql*C@BhR[h4hhm$ +i9@L*"G&+(*PXRGQElS,2C[P2bRp+rQZDRT0YjeESj"c@bRm8DQV4j)SGQ4G9*aR +jVq-r0dcUIk2B@6p0mMpDl%Ia(hcRR`XUq4Iifd,daTri-I(*Ie-c'69K$bqhTb+ +AZK(L2G2dPPNiX5G(dUS4Q65,VaD*I),%B$-h9M,8dSbPIJPM#GIRq-Y[0!(3+Id +IM`@A6V1$)DA$EMI+r'mU!Rl5FGU#8q"5-$EGTX,q-IH'X[-l)8Y+(@q22S3"%2" +JfK!hV5`Sa,B"!m48jiqj)d6`i$3V[Jd,#RK3bd[5p1EVHI8DYmj0Ncf!,M@!X(J +JI*%E%)Yi%23&h@Q)%1)"ppF)(Z5QdZB&f,NkhbZ1GebRKAXQTkecUR&Qk'$aJ*Z +Q9q'"q9YFUEM9eERk)0VTVEh93VPHM!NM%5%HC+lKJE"+*aNBj2%JUrZ,,*@H(m5 +hY4K$2+M6H$!YihSfPq%Z0@p0hTkm,@i0'8)mU1HN8QRKXR2ejHpHF,)mCpb-)T2 +FmLXh%KYeprjLmF1G(J2lPF82HiklpN3%`BNIpN3mUq@(IF2Gqjp$2a`YdVhA1I6 +$(6VGqjT$2qc2f(3f(VfAa[KKImE!2JkaBRp'erlXb!rhPR6YiilmF"Y5ejlYb!p +h)hAYG5ljbHh2H'#8hL'r0pN*fdZ-VmPaXcaRA#r4%E,Aei&`8k[Bd)a,d9BNCjb +0J15Ze64NKJN3f33j%T*6[TVPkJSAa8fd5E@`l5Vp(ar@`["M05a,`G`!FFc*T1A +'ZjjA1'QTmZENdR,lhA!F4kENYVbRmhTS[Qmf`M9a(0DeT6mj@&)&hqp3aRIfU88 +E)b6-(JjemBe2&K$Hm,&3PmL2KVHjQ6Mk6m5*ci2UI(iFcj!!@`Ar%-F$#ANiMJI +@42mqMK@"$-9adUkD'RPLVklADdqAaVkpeU#q'HHD0Hjq,$bfUJ@%*0XGhY'A'$R +H"[[VM*%6@if$E`Y[-c4ak09mMqI$R"K2(!q84q%5V0km,X'G4Sk!114-VbIbbJ* +@'dFI%Jh&dGmPT9bVcm5a9*8ZiE6Hef@!fTS@FCVMZkUaPJld@[lNIM1pDr$r(rc +M$0q,!6JXL6M-f9,pVh"R@19a`*3E)!kH1A,YLFHD6'AFiFe2KHIqB0cPkfMe2F+ +iHq*`"M$Z3pCYP#XqHelKkV6+Fh``pqkX2X,F"q*BaG`(iPM&h&I'm6[QASfMbYa +jbY`6qjGXM3&JlThjH)bjpb4f$A1[aV'+ZAH'm5KcVmE4cp`PMJ(blM2hNiEqYqk +G(ibEfq3Ula('h4d(ca(!Z)pdUVHpepbeDZ8Xikll%@151&BJ`d1-D5#198aRC4b +rBcV91"j#mXii(NAbDKcpS#Ga50f[C1EQ9X$H&hBNHLY%iPJm+,I*GMbjZ'iJMVV +-$kB`XjP-5Ll6IIVp&[(N0$iUF@8F#kp`4BQ$+qjcpr&e9[5*YPb$1jcABFi3F1P +q6Mi84a8jqqXXU)mc,jICEep$H1K*,mD-$,Z2mkZI49HTZ`[9rR*GU,Z('pe(*d! +FE@AI9i!B!C!!lMJ%kmQVGkP2EVU'6S"mQ'kI%b!1fqhVLf1SfhGb8#QVBlKEK$L +@iEEBa)H0Tk%IGhH(-AbUTJiE4ljir0KHip$l##Cb'h96&IB[0BjmBEdar&aiLkb +*ia4@88mq"ZY-Zb`,DKIVP4D,!D,H1,K8aZ0"0m0eElSX6S"k09d@*d!F2"r1j!E +V*r*UiT!!m["qA"m(Ze@jalNVJA-bZ4L!V3"XdUp5$fpar-6N3e[frSZp+IrC'ZY +lKX-i!H+BYGhEPS4m-Uq`8RI&c',IDqG#aCIM3NrA4ii,29f[lS"C-F1&K[%$"$l +DUIe`!QF`Vqk*cM921G96H69ap0m304K(GF6,HfImfiaAhaYL1QkP'DrKjeLDm9T +5Zma1UhPe%8ajbkcBm(1!c)Te!XLMXf,G51d+Q"@,,PHZ[%cm"(0h9$"6*`"bZK@ +3!'V`$Hc*FhrdG"`ZQh,kRS1J&q-'fT3cr"a,Qh+'HM%GJ6J*0ZkXH,TaCm9cEfh +FUBlrHfea&I2rUaLFHf12`%2-Uc11ajKA6d,@-+pU(-k*r3K$c+XM%,I!RJ8r$R1 +*A[FM#,PZa#F&[AlF!)c!a1'8f2Z`!BjbJeaZALREQ&GbbFfRe-E1cDHNiqAQ'EQ +"I81)B9l*FBB(DqH9A'4JN!$(!kIqcF6qCPmhkjf6FYlN[**GMQZjFC,RMVYcNYP +90mfVKCLIG#rG64RF4ZNC@!q*j+b3!+01aL$N*X()9CI%#NI"F[-jAejcHbUl[U[ +GE&rIfGq*hlQpUIlfH*8##2Sk[EH@1%qIR-rQL,f&A4j(RR-G,IrCakXIqS45Gpi +eib3GaiB,j5Hp,KG-pmJ-5SeTF-I@jDN0'I44Y[Y4#1*iYQi*0Kb2"fJ%KSdq6`d +B`mD#a!HR2+5q`MqUeaP3`6)KP(q*m#Tmempc1e`"(C5a!q%Fk0c4e!ZXXV2RbL& +YICAbC,NF&k$VZD"U"@q%EmM3@-d#@N-DX,4d5ke@Q+3"f&Q89`4f-66q+km4%Xj +I"pN!#CK6NM!mU$ilbRMd"*+3!1k&k3rLRhreTM!Ac12#Y9QGGh450M8Rqmh0H21 +@pTNh'4e((c%kYUdGb*Tp259eSjRRhr6f'pBSBp-*TAlkmfdhE&4$jN"@!iFI9p, +X15UD@a0V(RZ')k&',Iq*-`p[RG!(XUL[f[AEYqGd)1CfII6kmP$099FX9EYLA+[ +M@*ZrGI&ejAr3Ah[FAAl@eXhUQZf*`jPfVIQKiBAIjQ9l(Z"[j3V-Rlm8kK)2(6c +%I`I6$8Sll9NlFIY[N[VS@R06[rkej[2p(5EXrAUpEc4X0lV!VM8[ih6P@[-#6V[ +0Vc9h)M"h0p!PMhh-hID0h3(-h5eeVIP`h@r3-PaVIYfbTp"9kZj#YEpF&qVU3ih +ZSa-JM[+ejZ8i+PGT&lTLTQ[S"-L(kIBj!H))VMA[M@1SffFZP*ip(&eVRTbIf!+ +pA'ZHA2b"%I9mBZAqPp-R%qESDEL8Dme(RBApja)VlpfA@(aI4b-1Fk&d6ci'kka +mVANjMXUejS8ibYHDVmq(kE)i!HV9G&QF!((`mYHDVmqVL51qeRap("Z)0YHD1qB +fF3ClJZjmHlKTf%'eLE-&+pTeVj2BhXTk6DkieiaIfm4Ca'NAr#E1TjLl#fN6TcI +1h"0IIVSM"S#jZl3fF3l8r3BY`bE1Rhhb5f#Vrlea-0H&f[*SSr[S")MMm8fFaDk +Bk4Sk!I*KZRe1J$KXYkm[MU&ZhmT0R([FHQ%6jal,cfrLl-R(B*dp[SQc'-IMQcL +,FCJZLa1JANfAa3N3"frG*XjLANdFrl1*XaM("U,0*NkZH8f&IfQXKKN4(hF,YKa +0HMrR0JdH6"ZbUlER&',EJ)($lX!$qDh4H(#D&Gq'"38mU-8Qjm4U2D!bVeiMejc +@BeY&fr2Me!$#iS(`aF4j2*!!GXBCPZIB-@U)%1*"mM`HL0De4K%KT2-KGFMd +[X+T&bbYL3!+@F0lU6$jYTPD[FkTaCZ"Jm8$kcCRraJ2cYlK5FDZVFrA"XP-`HkZ +&FVdB%dBU3cc)AX-$iC&1-M$)id'GlMHIJ2@Gmi2iYTCcL!Ie'JqQC9c2hS#&VN[ +0@j1h*fq,@d1'%!mDS+UPLGLBcHEQ-DALUIE(Gacq9D@'BJ*fF$LlhYL%aSF)&hC +qI2ZZIU9+[a[VKJXEcAX&&`l3Z$#6BplZYYF+%LR3%12#&1,#r@0RAYqcl%4ldd% +@-Y1id!CF1(aXl-@IMA&$"Aph!VM!lS6'rkY3l%j`b-h+Tf2RCZ9Aldj`Smh+Cf2 +RCZ9eGi*$EPDq2YbZiUCj5DcQ&D`dRr2P0EHRXZZlfXhfpChpRILGfj[UEipA+3! +R$!BiBTkJ3-)`hJ6#*,m$hX+G!DXTeZ#'EQ&Za*p*k#`)2ZA6NIKd``qKelAm-1' +HEIPK3m+@$6r*D$"HhN902fRmMSRma2%NQRkBP*h8m*1DM#H+QkdZHmlk#B)c%lH +H@Yhbdi6II9TqU1*p@hl'i(Gf9$mG&5pqaZ)h29GRSCpQr#kJZCN-kiHE`%m9`0c +lIS5G$9`2jMapE4Xb!,pVmNkP9VEl#c%!Ya!j1*icES1*-ki(lXCj'dQleKdh0b, +AQ)Nm6EB),JThK(R+PHea5f3mj(6C2RQ'S-BPm[pbq9mQrjI+rp*`eX&6J@bj28[ +XcTErFb6HFd9rRX6l)SRra4,[q4,r"I,r%[&hB6M$`HNp[[iLmAmaTNKj,`[MGKA +-a)b3!(mL`K'C*&Sb4PE)"&NN%r@3!%b(8!dMBcC3JDcC8L$6C*K-GfACVhHh`U1 +k(4KPY!qHZAE1E(bHNHlH8YMK&(1HZhec2VcQkQJNFI,&!'T3EAhfak!5Zqee#q6 +ifCQHB0C*r*JD$[bNAjYe%MpEi(I'V*mJ12(6JYrp@hiQi(GQ`dpQdXp&66rFTTQ +Ef5VN-I5c*Ai(CTh%cb6L9hA@5IaX4Dald!pE0EYRf8)rfq"hB-E+qYN!4*LamRN +K2"mH#GH(L0K0j&(h`qr(Z"GH8LeXZhTZj2MiVQXM6fMFDcdV1R-YHcM8Y9mEQAM +`Bk%ZN4mGjVq$kGV)@jp!2jCk[ICdDHcED`hUQlq)[Y*jMi@1@X!hXYhKlGf*NH0 +YX,r1'$Qae6MiYYXEFEL6VShXRj@91!B1KG3V)8qqK00k*D3"DQYDa'RheT@3!-U +iHmDZh4q-f`&dXl9MJEQlJ@kfAXAF%rZAE)d"B1lZRjZYh3+h9JrA[32NeZT6Ar2 +ak+h9RA(`(!'-ZhUBDeeFa9fZcSPl@Yd6GkFk!Hj&03MMP,M2Y#lcqYAArXYQ-UQ +HDr$ArAk,H'fk%PI'XI!+9j3i&PUqcchP5l#DYTMFS+*2Y,I&"14eQ$-%A,UIN`r +&88A1rMS,kZ2-(ab9r1"Vf'MYY#jKc-L`qcLrqPPdPEUl81d[ei@kHlM4IA3#a0& +@pRd&L"%!kBj$X*kmHTIkj+CVk!6)KqRf13(LX0fq[ML'ZRdR"j@b1SDpYX5aE!6 +@bibRS4phGiFaI+UQEMD1Y+mkYYFip$k#b5CBNVJ)pLmeMPcrCmE`Fr#RFCc#+ZV +*af#GDCGP3HeL[G*P-8$8'mI4[6P6*IGFMQ-`(kE,iJ5S9p0PF3,%`I2Kc2ZI[*S +iT$bm(pI(`@jepJ4pkh"1*KF$X"Z1#fQUe-0E($maqG#@[IpLEmTrYXEkRZ%`6S! +iCQhhYL8KRm`VV04aFL'pi8*2edH1#ceGVqk!MV2K3X2i!3)IlG4q1)%cQ&Ih41H +DTjcUUEbD11"8`r8aX&PcXrLh'Dqq0m4dh%ScAX22X66M0A54I%FJMS*CXH(R!*N +9k`53!%GRaEU4fK8`+qBcpcQf0c6E&-h%6c"h4`8cG3)JTeX"U3CIG9'1!k5Rih$ +CP02h(!5p'$I3TTcKjeMDP$28LqN)a%A3LhR!KflF@I(F@aYhUZ2rAPYFaIcr+JE +RhYJMm"$ckScM-HE9Nj!!0FbV'SGcBMr#%22U#-3YX'I"Mq1Nbh9'%(+ePNm+H[f +i!4L"LF-TXII"RED*mp"`Hk0jfCl``Z$+K2r2A`TeLBF1(Z+rJkQrZ01HY40lRC, +8fle1YY'NQcMRqcY-f2[eHYpSH+q"@fS6jqrlJZ%QcM*19cCa&R$DEEk*diR!h0e +!8pTpc0eH1Z%1B1l1SdfF$qr3PZRUiEVIS'ABa(RGXUI39HVZ3V@rA"IUkN10lU- +6))lb*Xjb(*@0Ji@ZQ1ND1J(bBETp6S!iJNfF[A%-GI[-pVRC`p%QcZ6ma"ESC40 +RF[%(4Y6cLCAlAdkI6*LMTq&50R'11J[lcb9@hVX[XILqMNBFC[YF6ci'kkbmLE- +F4f86Cb'1mLE1pIN`A4BR3,fD,SX6)!jHIK2RqVbD11*0R1[Mf%#dfF5j)Cp`#rH +'X`CcmTrm)b)!&D@,"NrG-aAKj-%2"d94Z!%a,@'m+4(q$RKM6&LIH1b,b9f"#dk +CZJpVG81"!5FEql,CrJQMG(II`2KQL-5#rN,Cc*IX-YH`e@lS6AI"Cl2m*q8r*Im +EbAp0-i*N@mB0A-mYFprjmClPlDD41V)"45'R&Q'0KZpU3EJQBk3ibFKr(IrPDT! +!"[P[&$[VTdRq4i[p+2kRP$,j,i"Pr03Up5l4'hrLam3Rrdh0C05mPIQAC4b,kei +Kc)C0h3MaRQPkbbbNYebDda+45E2iDT()*dJ-0R0M*8-Yc9MUPc!@pYmY+&L"Jle +VE@k4LaK0!X6CT[)r2R5hlr`(YRpeV2Nb(rh)hJPplRhMdI$pkY'A%+@8p5DUiJI +6Qi!1ack3!%ED&EE[6Ii'DJ+l4G0!-SA'BVGJbQlqH"`5AqSVNGeHf!q6fD[MLqb +f4ha*fNPmN9eQ+8YLLBHV+*+RILC#mZ44r'bPl@lNVLSP*-Nl)hml$""9e1jfr,a +@flhbMeMJ#MVXNCqPlIEi$d+GEeIl([cXUHf1iFDqY16lD2b-d(C04-q-YXYbrmR +1fZjbVZ(+LYh98AcArLPdGGTZ&[c9L0dPIm@FDI8IKCpQEEIT%ZJD*'kfTilAGTr +k!Y(EYb1Ac8HehHXBm`LT'mPIBVkfHpi[%8be$VKrIa0YGrJ+k*SN[Vq+kZEMRi0 +1Z9k+G6C5fhhLYk!EVHff42dQVY0fRrp0')b4ZZ(qJVfeh3cQEkc%mCNSMLrakQe +Xj@hp!Z,iYVElm+-`f%Mmh4c9`3FBhcM*(pXP08rM2J(GaYTZUc[`Fj1fDlL21IE +YGX)DRB6Nqb2%J%dP6papm&jYGmrGV#fYearKCdGYPrKRk$E6G[X#Nj+,Y&e,(`` +fehCrJEM6B[I[a*3YY0d0L'rUEfLl,Ai0"Ld5(e[6@LZ"6h`0@HPT`-[-@bT"Z28 +bY,ZH1GZc%T6G+EXd,b!C"ArJEQKAfm6,(l2@*dKpIc(#bpeZB3l8EPRNliS[3cG +4q-rj%9lHpK3,A[hp,-+c4Gq($Nb'!S#rLTI0cc"@c3HiShKjbd%@[1B[a-XV(S& +Z'l%,mI*@BXDffZijU'H,PrGb,G0Nm8Hp+NkFqehSYY0fGF3MaFXErKUk2p*f2m@ +raFYhXYCdbp61pdGiH3jh+i(ra-'riXIC"k#MGX!KI'MGl2mpBSEL&VGP+-lpj'% +#N!"[Ya[`hH,PkmmbB)d2EU&iqElIJfiA`BQT84bh%brK@I*frfL%Paka!6r%38e +S(Ga'E+,fi3(JZ-8$X'!hEEFrm9(amYej"UCf8VX@,hrmac$B3h!DI&5mI-[2XpC +pZdRNMiUAep2AAYVZi$q*m2+H,!bQDEX2246KjD*[`Z#C'PCffK6Kj9KLbGk5ekm +em6*X1Vd(H*PH+hJCfNhk+QY,m6+dUqpXiQ9SPb3Ae2VBrTqJ)lFNK![0&5p[)(i +r@qS*(UMqM[Ppk2E4GJF3Ha3[AdFmjY3%m[I,#-qqM0alc!2Pr-N),bGq#$VG8TY +q1F,,CBHKdjeL+I!NLjI[C!jeLf`0HBA'pmeICJ(lGQFp(Z(PjX4[hGCB`reXLK- +IC'`(50h!Ka3[ac#[HQ(0Eh$+NFA,HmPh$p*fCqk-m2)meX3XLB1m+RkdSFrJbDl +U1YlLSA9c0R&&Y`42BRdVcPe'VMP(fme%2m$LjAXq6N"4RJ'1+&lH3Li1YP#UB*l +'F59jJlC6cJ-f@l`m&4M[rB,%)IQcGA$H%p$"kFJ(q+ej1T2B!$E+Hr&SK*GI)Cr +@cYRji#X@,rFL[k(R)1m#e*r&brQmK!Pq5YbA4hLj'E$FfkcY,[jdK*HhN!!(d!H +3!0$'`*r&bdq6-p2I%,[Mrb(#bldr"3YUJIMJm4B[YII"Zq38-8ha-V5E62iNZ'M +`-V5EmYY0[!cY%Z4G@KpI*aBFSqfQlS[`FL5aj(Q#Cq4&rEf@(1PBEAFJDe(amNc +f4DJTD!Xmaq)@r2)ib3IeV(MjpSp!TbG26#%R8lbmmPqJdk-NNYb-U(JjJALJHqj +f24R&G`(jVZl4H`RU`Z,PYY`CU(HNl%jX9TbiLaLR+p&hB5p%mA*2FJ2r(T(%JiK +ch%PYp`2bB0ek[`ciB[(bApK$d6f*Zab*m2)UaUTpY'fIM1VQr(q(6[IVlI,R%Fl +Gq#hSp$D65i%,&LrI$-cfT(qhbpp&H(N-Hc'keh8+qciDadI*DE@[GcNiSXA,,l" +R!#lJljUS$Pj0,0I0Nj1"3cC2Ql''kDA)qqJR)lcFQER`qib*Gc`BiH@9a!Pr(f, +L*qP2mI)XpR4drH%8FJM&bbq`$[apMSQI!TjD[2aRFMl`A1`f*bp8ZaELk$RDlTq +3!#q,PdHa"hHZMQp2pKZH,hLT28(mI3fkG%R`-QIRhFJq`[D#Pk(G92C&,&k'FA` +)IC2kHb["d&5r-l%8pCCm6kAQ`epSPpcj)ZRTh,3IJ!H$+l56i8p`-Zfc4R(mL*c +eHX40(HAk3)QELAG[&lZ3!1rZbVl1C,(,iAVL1H,0Zm3ZbLXmd+qR6)EF5eF-Em[ +HJeqAfAGpL36*YkYP[ka'qlZ2r3ELKGi+Rr-'YGfpj!4`9H+$4fPm&j,,JS(82Id +Q2ll-hj!!keiSI!*FpZ2,[T1p*rSHd&*mDhc[B%q%(L$a`4-d[P1*6aGVZeRX,fK +mhb'hJq06#0+hX2Rl'$%GAL2[&Gq*iVZ&ZAQja!Fqq2&PhmcBk+H"+pKTI@c&HVT +8ffAB4p(ir[-[SB1r8-,JSmEhFr+$2pATZ)Gp)SY2fTXfGY+I#2SISGdrXPGNkcj +RjkeN6"DI3VZYLI'#"`E23VZTl-Rjr#CPNG2#@b@qM`1(Y"q3!2S`Bi+,LGdGb)2 +LEZS@FP6iR5$2424&,1kqLM(5ba9r@j%h+aqkRG`1lNcF`P19$k8kL,9AL"fFaZF +CUAh%2rScf0&h84jc"hYYVp(aEBfHJI+Ke##j0eaEhPcb2jm2TEDb"`D2!aITR@X +F[f'[aEpP+,%-26cP3kNKFNYi-IlS4fZqPj!!%`Z[iE84Mj5[I*JFlidkMQF!!j4 +ITJjm$4C[d[kbl2[iH*!!r![f5Um5QJeqqAL3!,VZDHMJ+I*QN!#Rq6`ie80FHE2 +%$ArbHA$UqVq&lLf5ThG&[1dDaJ3R`LlX%hq@24[i"A($LAhHPUT`YZ&YfZjQp&m +XEl[c$iJa[Yefl(%UErXemB0H$ALFUi08NKK2ld[mh3*-9Gk@1M!'#f)"RqKhq2f +$e"TL$AQM[Z%EQUIVf0Z&KmUlJEALp`p5Gl*R4km'f+([S$cq%q6(j%IHAUceZ49 +rTPrHaYkBY3YjdJcb#@XAmU%Cj%R@,X*Aq-3Sm$0iFGJ[1BKeq3Va&r+YV`,[Nfm +*mq5GB$m-h"1lli'ATYkVl3jR(6#,3AcdjAdmp[DcEQl5rZj#Mp$'m5PL#Id"-6N +F[-A'F41j2MJ2G)#A'XHh'$FF42`GaIk6aV'-'%'HaH4Sp%jX(*Ha[mZX#R8$TQS +Fah%@jF[Dhl(NVaV("iMTp0l&j$Md0@dFLmJ4QHqJRUKEMH-ZcP[FS[fG!1bhFAb +5I46U9%a13Rr)a['AV%Z`MT+N"kpa[)#B"3q5GaCaH93&&bcNDT+kq5(lFpqY@00 +A4L4,i*F*h6FDrB[Xh6"V)bDh%BF1dhBhXdp*he,XrS(qrdcEIB+FQhk,a,%!r#( +4TZfH5rb',i[G+F!e'pq,L%0Ieh(mL,@LmHh$rJ5F92bG5VmDhlICXb!@X9X)EQ( +M1i+e4"p'l%k$$a[I%HaYI8[E,5+IdrMZCKm&2S8GGD2aEF)krSkf@`aqCH-E5kb +P"X9Z#A$IaMH&f(5RYMZGpDEahI&ek1J*L0dCR!2aia[e6FkFI&rXT%pTicZBR*! +![U[B,3AZf2MH5Nbm5pZGbEkiaVFAqhRd2F6Z,2D40Ap6f31'[f!Rm@KmSrk0R*! +!2UIhr`#3!`d23Np"Ae4ME&0SC@aXFbkj"cIeFJ#3#!Fhph4`f3#3%([+!*!)rj! +%68e3FN0A588"!+qqEm1bEl`a!*!&!H(X!*!'-DN!!"d(!*!'R,J'q)lh$M+bf4& +qRH6jN5fHl$0TjG1ZGc2#0NZbMmPTpVDTQpkQAl3Lq`KhcI#F(TlGqXEa9rc-VY- +k2lE0M*(EAZq4rB346MECjlQpcMbbcipY`RZf6h+EE-+IX+iME,mGfi4hAK*1pX$ +HMQ`l"e%XB,a!9'Q6[Y00MR[ENe#bb5Dr#5HE62D4iEk9Y`,36lXqVeJfd"cPjD" +Ll[Gl&lE2KFFqqic*[HeCAYlLZE$YGel(ZLlZlII+LcIaHUphdBB0[(b[3EjAIJl +X1MX[1bFjb'%6Vr0kXj'$TJ"B!'IP),04GVh(ciAGCXYa`VmMQh#bbH,&h3#-i$X +!S$V2ldY492BmEar2bdhcFRI1p(*h[GhcAVk@Q1Ybq4a(qkf0KS+"BKb(8Hcha0( +bS&4[a(T5[VDX&Y6&r,r#mikHkAR(#-qpcfT)IeGklU+VdKYE8l"`GI2EFmHFBQX +dZV[dlTFQ5`jb2Lfh0VGrrZ`GfE5jDFpjBf2MBImePV-2U0FGq9jkZHHpDFaq1U4 +P@bhXbPVEhfrCmE93AA1E"03aZI!*e0(YeD1mr9p5L+V&8V3l4ahH01q!h-EmkXB +j5k,bcXhp1,GjRfRHG0*`JqIp[0#FSY,)P$+S`"N*,rSG+ZL+h!fj,IP(YPC3N!# +E)Xm9XZ&pb&V1QmVrU+9(rkpS3X"S&9i*(kqrTDS1[U2jYlSRjr@12dprMq*rMV$ +,V9i%HaArH@'rP@EFITrK[e2BAr%[$+6X2I1%$A#B,qaB$JF)1j2$!Q'2jE"3f08 +F$K6f*Y80f1pb@#6X5a`1%[DI("D,"-Ra`F,ZcZ%3B5Ga1&6B"4`1%rC8$NZ%[B4 +$Pl"I8lf!IC4$Yl#rj0$MX&DVM9jKbcMd#EX[KhjKCh!Jrf!r`i&l`*l&B9$B+cN +-#IY0$XZ&r5'(B@&rcf&%0%`0M`SM,f2#U'Pi"H`d$SF,JlU3!,YJ[(ZP-0ip,Zb +p($4['1ac(+JGX(rJF!q(cE'k28)BEcj5f$%FY+)ff)-ih&-BR(B[B5MYH`[l&I% +2f,Xir)5`6h&B*Ha[1Gc(BHh')p38'2A!Hm')q8PKQcM!,@$8Yq&-M6f*`fTKeh" +B)qaA1D`9pRi1a`NM"qZ%ICR$mF,qQF-*$XXE0jdSM$Ui[c$H$mq!8Am2%2C)$ZZ +&2Cf$m(,Hq1CNB@rNX%(Blh$B+1b21$a3f0q)Uk&I11"8BA!!q3HMERNl'(QR(X% +iRbi-h`KH!S2r(LcX&cM!b@$`heR#i$rY,4RXDa`HiV"1irL(#S-6caC'RIqd-(+ +a@GK@$Jm6pN`1j`MMhHF+qh81j`Rl!`i2&`B22F*KFde,R#m-6U6H`FJhY3E'qp% +bB*Gb)1GJcqG`S6#YNA*9BH4B*0jFUr[(#2Yh$[#frZDC(VKB'(a(2X'SHG&[mbb +2D"Q`bcJm6YM,1$aHf$XiA#)-[L%rB&rPm!5(c6I0*ZTZ[VhlLF,JBMJ0$&li1@( +NQ[H#`6'L31HMCA2R#H2Ge$[Blh0iQV!rim#GB0rNJ1l6h`'Qaki3KUjiKM#iN[S +&Jbr3&'$NlNTKe249`Yl*!Ii(q`+(j`Ml&`l2GGJ#dfES2M"U#am6$0jkRM"U$`i +(ibjU'Hak$Lm3pM%1,a6f&aaHj,#&TQPI,!`0"[H#S41S%6"iN!!k"D2Hb#(B,h( +i4@'r`3%Fl)XFIPRBIh"iZF-10$fTA8D$SCI31f$`1jS,$!j#8i04*km@KLkj9YM +[FEK1f*pcH)h$lQ,Fm&TKF2rVK&%hVaI'@hN,'&cj"Q(`!PS$M(T!Ai1pK30e"2C +"$R!Mf*pbH,1`Iq@!,Y$I)Y2Eh!Q'*RZV-26Bfi6"rI!j'2PqZc"b!"H"88[`!aJ +ak%%`Z2p'B@L1Gc[X)02Fla&'lUP(-,3VfKN-cBDH"81Ii&H!`F@r,3b1H*m`mSX +'"2Xi"r3A'(VV!`jEE"i(rJ-B@ThD")-,U4F`h[SKBA$kKi94Cam44QfJ0F#)KiI +"i#Ui5hm(QlBM$f$i#jm3KUlqT$"iN4S"3fqJ)m$J`8m,SjlaCm"iffH&iG1JmI9 +Rl*(2#d0R`-pJD$Mi$3bGL4F"KZC"Ni,"dAmX$,li%f(N&0i!qfX1H$Ak1p6m)l3 +9'2i&ZJ--(3krJD%K[b)-EB-Q!S-RU"%`Z!BI"ScD)*pJe#YFSEr$c)ILl@$`rGH +&8DrF!iC@`fF!3rrmR6#i'lm&$1j!Hi*4!rmS$*hd$BFY-6m!(!a[j9[#d)AI&JE +rrC-`p!pq!KKeJ`i#)jG`,"K[rMH(GGRld+&JF$aD!``[!1m,$)dU[QBAZXKCIPh +`MA-fZq!!*Iq[Le`VqAp,mH#-A`HQr5!PrfmT[SD5rlF8RDcNrbe&MbRjIdZT45A +rEbPmT16r,5AA5[jI0hkFN[rAM6qKj2peSe'8r,pZY,H5rpH0YP2brlVK-#ArVaX +19I,rZUPR*Iq[KcFVqAmpq!&+rPm2[+,Nrr93kdVqA`mk@FRrkd(M+IPr2@J2*Iq +["dj8m[pki!)PrkqAh#RjIlei2dVqAbqD3mRrkd8(+2Pr[@JGihq#D9j4m[pkd5G ++rPm[[+,Nrr95fdVqAamj8[,rqUaZa2rV`d[526m@dpk(N[rAKeCAm[rkd)T+rPm +IHNE*rqZ$6jAm[hlUA-RrkbFA5[jI2jTBbIrVakp4m[rki@-PrkrIkNEm[hjdQT, +reipQ82,rqZ&(*Ip[J"T@m[m'M'2%raZ`A)Vr0i$[Sq6r$H![+2Pr!qKE*Ip[!(f +Pj2m0S!H8r,pPF*!!mCE"G2dSqAr,L&IbrjEKCbRjIm[3CdVqhc,dQC,rYmab+Il +I-UY,mImS95ArEj!!'PEbr`D0Bm6r'm628[,r"Leri[m0@Lf+rcH)KP6br`E4+NV +qhj!!mBViId28YT,r0f3F,2lIN!$9Z[Kr3rJX5[lI%"k"N[mhC"`TrYq3!2'@q(r +,i8FPrfmjYDVNrbh(me$brjEM54P2(NcV5#ArElPa[2Krbp'@5[lIFV50N[mh$#m +VqAr$m)k5rcG-c5MjIm1FPIbrBE`L*Ip['%p(bImE0ViArfmBlDhNr`fM-jAm[f( +dNT,r0f*j&Ip[a,KIr,m4ic$ardE)YC,r0i*18[,r4[#jP2br%E`B*Ip[""p"bIm +E-6i6rfr%q%cm[e(dLC,r0fV[&[p[&-ib[3aJZMk9r,p4hUrNrihDZmAr'mAV8I, +r4ZhGi[q0SSH9r,maY*D5rcH'(P$brmC-2iRr0dDZPIbr-I`q*Ip[$,p*bImEXhS +5rfm-rDcNrkf`(N,areBBpiMrY`)18[,r9T!!4bArE`AHLC,rY`,r4FRr@f%e+[l +I#[5UN[ph1$V0G0Vj@6jm4Uhl1Z%@)i("Rk*1QUHU'qGTeSP$ej'fF)f'$dqkAmP +q54Y6EPpkXmbI`HHVPfKKBH$8rP*j89!F#Z+H(JQFk-[kc0V5&LMA*p@D9qYAe$F +90)2CjLAA3l6V@mbYSPA,Ac2[`C-[ZrG4idTm"A`'2!@m#V+$6i%riIU5m$2)$Gi +%rJ(H!Ti#IS2i$pk#Ve!K1Km,&aI2+,DALdN5*+fV3LQ*`RGQCiqrZ1eNTdV2h&- +Ij,Tp6raCC(mbAK0!X9BTKe'V're[l!%G2m,r-2r,q4rJIaRrJrchm*rR(cqbPrp +$q*r,rd,q&r!rTMBfbSd5T$aG@FV66%P5VT3Tj8Mj8XU8SpK!hS22N!#$*60PU9R +"raMr9!%15*lr3Ik(q&r1riK9K[l[iEqArlRmBqaSAYaHL)jFSka("%@98#&NRbS +Jqe3MP8"PL"(Q@IJJbC@@)2fr&+PK"B9`[rX[#S4)'-MR8QF#jblTk@bYaF&TS54 +ZT8JiUBCU@E49YDaG8*Je@mj*mFlS+qL8L)9GcqVY,)aV6NF!h5+ipEJXZ!3SGSS +I&i@1%aa[ZP(S3X'Y`FR"@F#"31QMr((,F1e`dh!b825i$*3$lK21'Xi0VJiZ$+i +-MJe1#Ui*$JQZ%fSHJX!Ca*@%I&$[+(881Qi&cJ%CT"-'T`3h!&@2iNHj3c5SGP` +lh%1RSR!3F"4`)h'JF+G`VL!Ph%GF0BJ-K`rR$5'*HiGVKd1(@iEEKXZ''iH$KeZ +(#iJE#3(L!L)2B!HF90aA(&XF39a(FEK-jaTZ!Li#lJ'Z!@i"ML911FiVMLY1,T+ +&ML&F(K`Ah'!F@0`ih$PX1JJ9p`-(&MF,0pJT4T`Zh&L)$`FBT`rh$RF,p`UR#RF +,G`P(#JF',R*GX6K@Z-8i3M!+EK!Z%!ie*)m,M@X'Td,i1&ELYNjp[-`(d2rY!IP +d#A3@JRJim,[hY%02IFIbVHlTk8UP30[mVS9,5b[#)4YlFASIBVKT0`6e&qYPJZE +hGRI,A*9U2D&hb6KhUIh4,Iep)d(C9%"q3@GA"kAIS[2"1VIUh+EcS6SICQINC[L +LPLQ&U"l&8ERSGi@$F6%fLRF2QU0cqm5&V3F@"R69"B&a8BFZFGRSe(Q"cRI4Uh[ +YM*3A[%Ii`SR(YEh-mRY+N!"%Sh%TICh,j3'keEfbDq,@r%[YeN,k60ia%H612Yd +jAqH$,VpmM-,-rTZNZ0X@SaDT3fU11U3'U6[H6Jl*'f9&59%a9!Z93T8i9dPjBE% +FVT)RIHYhpUP*[CJH,[i2SCeG$f)[V"EVrhkqCE0Er,ViTP[(S$4G!L[9c5b&U&J +p!Q-2I!*M*E`(qX-dSZ`H&h0GlZZjar-2EK[PJ+K'`(2ZZ$lkmR&MB`qV,5!NaMX +5ja[lY-8cbkil[i$*G#[*'8q`j4h[#F#-aIAR@PBm!TmED9*iSHH'0(Cr2e+chS" +2SI*&PRSpT(QKC@55PUaU&U2eabElpeZ$*M-6ZfSf5h3@BlNDeXAE0Tf+VKF%`pZ +iD#i!ar["A0RlBMN&f1#Q+G(FF,Z"6mmbC[BYVim#c1Xef*HQ`aYMb1Sb1+J[V06 ++J@q'hQJbFQbY9P+hXIf)Xc+4U5DYeT@[ECXc0(h"Dr#T#8(C*&!1KrT0+FDVq5B +b`ZF'H[PR2Nf#KiS%eh5hrB8)GFckl54)F@pU`N##@P)b'kcFS9AkC,b#TC(Qq`S +G9IElJ3Lemd#B!m,m$qCq-%Z$H5(-r@$H"h-qQ$I#2"(QI+!NQ1["2!rQH$#rJlN +Gc1YJ6JIc1CL4`6`1jR!`Id22*@'H!A--Q&r!h!,Q)6$[J,N&c#YJ6J(c%TL(`*` +#jK-`Pi"j"-`KB2i!F`HB0m#F!HB,-&H!H3,BYm`2-(-9k"#LkiY1*,U$k,kLfiF +q%$V(k2M#'D#ML#ibZVVSfU)VLbiLZV,SHU,VL+i[h'XN,Te&VUX)Bi)Z*,UBk#b +M`icZ)VU9k&+LBiaj)F`*B6i)Fd'B0m)m%HD#k-B@8i,-)p(c4N`a``rdHfZKM'C +Q[JGc2CMR`4`2jRF`Yi0j(FcT-2-jQ1@KZmlS#kFIR$j`jKm`pi"j"m`jB(i#ma' +BFm"m!qBD-&q"IRCQ+0$,cR`$jKS`ci!j"X`[B'i"m`UB8m"m!ZB5-)q!136-(f$ +Z!2-B62mlR9p4&alGBR5)dBe("aaGI(54d6d@GI24FdhE$l1GQ1R%,#IQb6#lLCP +0c(YK4K1cQHJqBbi6mjLB`m6m*GG($'R5kH@D9qNqSk10VLmklHJBS9-0+8AI#je +rG0YKG*)re`j+4aVGE(6%dB9'4aNGINl-d5e(ee[D6JV0-Sq)186-(f,Z%21-Q&I +%h#(Q#6&RL*Nec$0LcK#cH*Mp`c`KjXX`2iLj3F`,BNi3mi'B#m3m)1B!-Ip(cdA +#M'9Z!2-#Q"2!I"AQUM"2K6NUc'GKrJTc9*LI`Y`8jVF`ii!C,F`hB(i+Fe1BPm+ +F&1DM-"H&H5M-3@(q#A02Q(I#R"2QQc$AK(N[CLB#(B"dcp%Y5$FPhB4`,af8G&R +5eBJ)drHElN2p"Y2C5"HNIS[TS-48eZmdAC4d-p+K5'FLABLZSe'rfA34dX9)Gk2 +1Nl'bG*k-UF6X,'CQ-5Z,Q8V-hQ+f&V0lQ"h&6#aQFc&lLjPBc#GLcK1cVjKja9` +VCP`afiUC9XbbBSB9XkZB@B93C-B*-d-dIc&$JGN*c-TJ4JDc-CL*`D`0CQN`%i0 +C'-c!B"B(mb#BYm%X#'CK-!1$f4I-['$@"6-ZQ'h"6!YQ@6$$JYN9c+aJ9J8c+TM +GBHC(d!9+ab-GS65Gd&P*Kb4GSR5mdZ0&TbAq)3iDRD"dQp+C5MFPhDGdU',,dHh +U[$KR`H'md8e,KbZGTFlfSiZ8VP2A9HSCq,4B[pCTH#h9M@GP@rC+VACa+ehBa0C +IV!j0Y-&Y+j8,a9*EVGCC$HZY*F-h"BN3(9N!36B-,JIF%Jj@H1li806!FTe9615 +(&dX"cmceGAB[@GU+-cYZk%5a2T+H6fr[(Dh@`dU3!)DYINYITprHZ%@RD+9KqN( +bG*HNmGK,4LD5[V%89@TK13b'I'F"QrcD-SLkp3T*M[cLm9TTa+ijZR9'Dc*5(Rq +XVS!YBDPB$k-Ulp9SP+U*!D'!'q2Jb35iYrBIkFqD[F+h5H@jVAkMlJ-ImiJXeLl +hk#X,QD)+p$@Q#R4@0[5PL+dRrBDJF)i[6TXC6TmpG&4l)dc+6-2pM@J6Z'TV)R5 +*MAP%pAG@DP&Fpm&c*aA,j5LUNX`Y0H10YG2EMcikMG6JaQ*5+TC6p*4JPFGjG9, +aPVCh,eL`p3dE1lE"mCZGal5U39Ip9&pbKbQ3!$!['Xm9HJDf`5ic!NTQ3-2AQq" +6Zf#@S",1(Um%'`I,(0I9PD**C4AL(9TN-Kf48S-1%Ec)GlARbeh4Lm2V5BZG'K! +5KG5*+4-E2,BM%cBUH&"%9cC8#%rZ-E4(-9V1,$q[8Vhf#@6*h)mfd1(k[H-h@f` +J0M4FL,9NJ'J*ll0&%Pa"IPCCbX-VB$eZG9a1YJ*j!)r'JBiaZG'NeG"H-@'m-!e +1#b)3"BDT,#HRr*c)+[XF$@a'3*bAP1+`9RF&%3B8hKcGiKiDaTKm2'VR69'@NNK +6AD@3!++Z3GQY@BKZdaHY@HKU2Z,H03ZG+MJRULC41@K0kU1$L@Qq4h(rL9dGC`6 +9HSTcr83aV)+jqpFXh%i5J9"EXp#S5h+`VVm$8CAH@G4%pNDja)!&2SSj-ATDN!! +3"mG+PSJ0E8q&b+@&E'eSQhKmjSkA!Y2FaIGEY*'pj+QQ!$HdZ3UN3$Hd5G#MrU" +2c6FAVD""b`DJ+L4Jl@K5MbVqQi1ccScLSF4V+m&3@j&&fdDJTXedrflcqi*k2D` +1*pk#N!$(6X!6"Pdc'TD(r)k9pELBH21$`G(Ki5$fhCdj*l,@TKUACaTBlpe`NA[ +)TKkHH&&3pMTlrHiD-N1hV9eXZY#Sf41#DSSG&bE@lR6KS$dKc#+3!&CVpL`l2&K +0+Dr5)V2`GV,YAJ8@[bSX"5$h'P%mpr#lLYAKd5)pd%%4(1%%Yhl0HA%UpA-RC)N +pbQmE'JSY5SY+%U[Td$rJhRJ"[dl-kjV-f2Y&66k,Q`+dpYc8%'fkk3TMQ(`!R`p +K`-UYmbDDDGdH@3cf#Zc@N6e@aVKkI6j24-0iar-c#hdURbqQdAP+d%`A"j6LfSh +-4c-Gr@#&*R3G-(qm`Ae2%hS3d%+[M4d2i)'Td-8YlGhc1eVmP[PYr@dF0hDfpfX +BYLrPmm8-G-Qd[brLme1-&bJ(qAmqhm6SQhejr'%bY4-k88`2jh5X$1Z,k)fNXq# +@,2S)4KAFmY'R[IR2)f9m-jmEX-2r`N*3Mk-cJhK&iYYFR[kq9rIk4q9QA(G9EXC +TmjCdqlf,4V`Mr$ilV@NjI#i$!(6(mm"Y8`jIl!hl5paQ!e,V,BGr4mU`C90qlM8 +rp0DX@62qVqllp$*[bjBYfrjIbY$1#(rpq[92h((EIb@ReE[5YrXIrQrFX+&*`M[ +Gjir)1rc3L285m-2ZYmGpjD[85p9I#*25pTEc930KYGGD[D4$+`QqC2DX4Vp@&LR +0@PQJ2,FB+IfppDG+G0#qP85[Sj2Vd6Z[2$kUlRbM%3$ejdX05*GF%TcfhLA4F5G +H%KThhL@K8E0E&KQh[QAKeGU+fZ++q8Ykr#V"pFU5pVTLVB6GIeRN91V#cX!XN!! +q`A*8V9UM(X)N01iS6%,6,XNLAm3pNp%-JS3JimJL2mDa%6f@!U8-5R%ZhmRlLpa +B#3i+1!i@CSaM$$('%6%[9M-[&"E(TU`B"mHN'%I'R"K(4T4B#SaTT44G+DH)$j2 +%*A4BMUd9Nj!!B9)J)4H@!TXj#jQ`&!F4CN(9kSaS-)k-@6#16%N`SBDB!ph-J)6 +rJUJLp`9a%Hr&3C,`1-EPYI$@)YHPJ8&T"S(#F3&Zq#e!BfiVC94S+)K,15d)60U +YA96-C8&8a'0a8-`(F@4D'K&h&C+6m&Ba,LZ-`M3)5AV)9A&3,5mK4m8am&-58+U +ZL*H#U*L6JUL8M`TP((042%dP)D3NY-K+5A"%69PNd&kH"VS%&j03C+TUG_E4 +`9K*NL#X*LGQV9J2#2NP`bQ0*G%aQ5@M-D%PSdTkI4KDEpG2`DQe&,&I-Ad*ePH" +kC3RT&@XPC,iXFLTe)3GQJ4"K1DT@V4%P*U(&'3K4D%U14Ek)'G,aJBPD(bBl2C0 +iJ%2C&p1YeBqK6kXFqD$#MLJlR-*fC*I&Crc5c'L3!)GQJIUGF9$fc%!Fa%p-Y*i +m,`R56`X#XQFPC4@rV8c(mX"bT(jP%T8p0Chh8f,UdJ8a@CHL8iBSAC!!N!"%+6c +Qqe*d42R9i*JkUcHim5@9f'5)4j2qkr'"!XMLV3JSaF@PABU1UVX@2*N`9rkP`*X +C)0@4M3ZDkBlN669i4jQ')UFDLmkT"6E+)9BlTHKBm*5LL`+q&"pTq'T`)j9&ZdQ +')e8X(aHDQMiZVZaCZ0#5JqB#8k[)a4A0SLJXY@@Lf))a%d99dPXbMC,)b$B+)J2 +Mb%@NHX[&&49A(&C14UM&A%K9j,RJXKPB$+fNVqKc4@'0NNUmVLK+h+ii*%YXkRL +jZ*S2DfCD0Iec'ejcd'hXT)PV`qZY$$DijVrEf+S$A`LYqGQ&q064,N5@l'XTJES +6(d9RAVa%&pei'eAc(QaXeAd)3qY*+RN60Qc+",%A6(CYa1'0p&BE#`UKdk9BD5i +S4-EY"@&B,IQe&J-E@cHZDZb@aYFpb"UrTI%0ElM-F'P`cF@XFecPJT)GQ,&FU4B +DEQD9jdVKUD0CCVSdZ'C8PEQZ'Kcl@!ff5fpSfZ%e[L[&9chD1Z09,UJACZE8CU4 +AMUfiY@ADbfB9lR$dbZ4AMYrZkP8SX(a"dpR,L,!HhV505R4BMkik4&95R,bKCR[ +&e&L1E$Tp'8%f`UH59rAl'NaC[Q5ljeIMbmS9$Gq[cTVe1jSPA(Ar-[UXKGFG`$+ +*&LGc6MNM0VjZMGMJTMGLia[QL)fZZb-fZ'D2&',VSVa`38Q9&d)cr5fed,")S[# +bL5$KU8PL`qSZL3fZf54KE#09Q9&LikDG%RY$dbU*ibI6A$0,#V(E#l0UPa4#%lm +NM+YA3GdaXF%0bk6'Hk8,'UC*MIP+&dcD*QAZ+dAAMC-kqe9[b"4kaRpC88bD*e8 +'c1*,pNQC!d[4G31Pc)+ek-4#DI"JkBST%kA'K0N&04ZPcSA9'aTP@RC5-MUX"&H +pP$)KPNFRlh46bV4BZ@#(Re)KamS08ij+4T'0q#P"AL,+4RK0GeITXRP&h9L)5E- +51Z@XC03j'6qG`TUjdZ$3bLdll*8DNeE[Q$4BkRcDZ'5US'XH5dDXpIL'be+Qeh$ +JH,AVbF@@fTjFB+A[bF9QM8mZXY6jj!,6eUFSVY5S%`@(R6T4@05A%q3jDhp+3Z- +'SL#dd!$P3NSG8#i`EB'+ibSTLCUJA%bY#mT&9pUJLV(90+D08&(FC)%9@k'L-0F +,&FH8-ecUKR+"fefrG$*p'PqB4Cp'dYQhEQ8TF'2FTqJ`I@*9"`E2V%4ARaUP1(e +Lc*("dk+SkT25&&F'h+GhC!2[da[#!IKTD(8JIU8#+TQSh*0PSh*,Q*&+H$8V8I& +8XK(&CeQ)SX2A4f%HP18!@*!!9"qlmSe0Kq*aKLURlEIe2@ETdF)pE[9qZDK`$l[ +QV0je6r!iI3rlk"`cH3qljXbE[)GpG"C-hX0q1i[qQZr`([DT@E,V2E+2VVQ(!H[ +VXhYU%aje&,[BG%bqQheYmT2hX"01jq3pl*DcEGFp8AlB2@IVj$f-,6jrmKif%pJ +bH3mlm6cLZAX+K+5A3Y,I$+pjPHp"'$d@(RFci0ddkFGG(feJpkd$Y(1cTqel!U6 +JGPYf[(!TLerUAE-fD5Gj6p[h40YMYFJ,2`X[M)q0VG%qqckf6l6#V"HTldQmb2D +R[8Kp$elIFE[Z#4kRlm(V@cGj$hZ9(Gqi*rqd*kM[`41Fp4KPiFGCle$IJpFhkah +UHpJ4EGBle2IJ#FjkKrSH[,jC$drI`rjTXjkJ[JG2F0CMe2IJ#FjkPV)!Z0rE1r3 +-h-1Zc2SiUAqF,Y6hQ!@%Eli(Mf*5*iAhi(h1REa(rc9l@F*ld-'c[6Ek(RBcR1a +j#HpKhm1ll,VR+42hQ*d3EmiM26lhHDlAaZ`JVAILZrNHGSkCeF(k(RU"'[SaHJp +l+6EdD(32ZbXfG'Cd$lXY0R4`G!pl,ckL`92eHr`V[1aY[XIT$E5D%e6S(5I#m1+ +F"mmZQDjrKrdSh3lHl(AUqMIB%p@Y@-XqU'K22RDPG([IX0FSHi6bXE-SZT12286 +4PRcXPqQfF'@A62T!qGMrdfh+`KkID%FqGQYP6e%qGN0&+j*HmXMHSQ$dIU)0`I" +S`F(`"p'#B1c-5Il"f0f5R8A"f1d5I3I'$UaS1$$f9@@(86"f`m4,"Q-I8raL-(E +Sj,eJl-3TbhBTbbApRQ$X#)[2#mBHX(LjB1bdLPp,HC*,2&N`FN%[*KMlIXUD5#f +fQbBq+aKE1-RQCbffRbYq+4JlZj*c-2BeCIp3-,C9N5hq@k`ILhe%`DK4pK)&Brp +2pK)&)jIX*`V'6UEX+3V'9LAN")apCYP&&)cpADN[k!@1J%r!U%0f&!8M[q`U#XD +ZR1`U#XDZRZ`X#XC1X1a"#XC1XZcA#FD58Y`*"XH`CbFBHkl+$NHYPM2CZl29pN@ +eDcARpV&pEGRM%i`pDf9[k$EM0AEr"+0ZU5m`mJ8rJ,%h+,Z#JV(p&,Z%JX%6a)+ +a)bXlKi+aZb[j"'0h9hB3"B2rf%88M0e+f888M"eBl@BaZ4IC$UZbIhkEm36V9i( +Th@AY,Y#DSZ&VmJ!'9mTDrh1-QeM'#J`ZB$p4-1U+RDc!b+2Cc8TMl'0V&VI5'$[ +IJS1a2bRlfB+aPbalfS+45fS6$*jJEeX`q*[pEm(Bca@p!!CR`dY15V!l,KJmcZk +iB15*hA$"U(2C3V[GmN4pJC%,mJ-'(l#c,KKl1V'l,KKmKMB"BaGDlJ&Mee9ff38 +M6qbd#dDHi%N`G[U9VH[E69[)2MejdcEXX3Z'CT!!hB$bPM-C!*!!0ck5lB2b9Y[ +S"6$UK$eh`6LMPF$Bk9E@$-lElVEX"`X'(m!KB2#"l##90kk9jHMcYNF`(!M'[Vb +bX&H(D5eU&JcG)P[TG*K1N!"qP!lMA23,',c'IUYJm!MD$BbDC,GI-(,0MVpJ[%( +k0cYXVecfr39$0m"eB1bCbaklB1`-bceJE*K(cX()+h`#aYkfl-%,aMl&l--,4Ym +Ah!j'MYQ29hqGPQ2fi`9$6l!R,aLDLRej`G"#l-X,4Ph#9f"`+hS0$)iMEck%6bZ +,dKU2hY8[AE(6`r,T,J%a0Aa[RpGQ+I9S-rYJ&pE`HhbBM$bjP,SIB(20R9qdP,V +eKLq6DrjNeI06j)Sr9Flkdq5H2efZq`YeIT(1,pEj*6TI,QIm#MR`ce$F-h9qPYj +lTI#Vp0jRkrh2dAZIUrGIVI22blPrRZkp4[FqAflp#p,hqLQfd2-FE05QYT!!pVP +0S,CQkepNqiD$f+hCA!q,2TZYf8)mq%raQELGmC@FcVkjJNIIG"jQFY9ibdaFqUj +EhR&lrVlr[NEqGpeAHAIMRY[VjTDDEEl$,lICTVrJfjp!8+YiKUeTAqjFI,"$QXI +e*Y"$EKTQiB09`cBBqB*5dcCL326+6!'Qm@NqZ,$B[)d,LR``ar("U[U+fDdcQVI +*"-F(fMmiD"XI'*RJHH1$aG[ii**b10L866H'I(!`h6&UcS4VI@UGrCShad1-3RK +BA)c2QP2Skr,Edj-HNl#U,aU05d(6-Fi)rB01a`GQ)-DTSl9D&0IR,!U+3d'F0&- +6#Si2Y0qXRZ)$U``Z#*US-8,)"bhrd)YV`QSc-hB)rB2fIrS(HS@F#k2UR*l4`A* +BmMZVTI,S8*-`%4(b3IjIIT+2M!`52ZK`I[-jE)qdZGkmV88Tj)01p4p-4(&JZfG +YpBpVRGPk3V-eCJMjB#jkFGk8j`B`GVR*8RGqM$YGADXe!4K[acXQ#PfD8Y[mY1$ +YlkbBBdTQDhG-p(4THZfqGjcHVZQiGqEeP,lHA5qmp4fE1TEfY`k1H+8NQH(PrRL +qGf#[TXIUVf041JPd6E&FMU+U[bJS0kCQU0mpP',UbN[@fGQm3fE30Vk*!GrYK$C +C(HiGKf[DmSrIX5-Krr11(F0#r[FGfaqbpahRVDajdeI0Fr9kr6dMB`r2baZqq'j +QJepkMFFDD,Cq6dKVHA1b`LIHeZ[A0kHcdF`lC1l[$r*K4LAcMTfc923lC)ZfCQ, +Y1r3#Fjk!Gjbc*l-cH@AFVh[(0"2YIFI11E@ZA+FrmikYhXKZRVCV`H95SRDK+8q +IdGkp9mG`ar(2+TPcSj)Q-rrIKrki1)QI93flhl&eT+aTf-e2VeFhUmqfeNHE",b +$Ej`Lq9&H'9XZLM[FbUriq6m8YmjXlBlE&(Ic(Gi!aEhKT-AD3k$j6I3-l-f(jd1 +j6fEe0Z8qqiipbRhf(AZ8qdh[f+hFCpiaSpcGR#arJ(+[jq0lbVfCf&Z9qm`lpLM +hqM1qUpaRhV&6ZHYhc)ThUpc2'rjIhAXr&,IH6,9faff+Hr)GI*i!aDdAT$rbH(p +LdrRLerQmBY,[f-i-he&-XqrBSh4ZHXGZT62cMZm`HIdGhfAbQAIX*$hp$V-Br`a +$M$1)BcSqG#5Q+N5rip'$fUkLpQdBA,lh(Cf&CrGJk#J8fV9EajhIIc1HhS@85Vc +T(9[qTKAe1lD`1QPKNG&k4CmED*q0IARGTaN#,Ee6Nqppa`aclUbcS$kf032BUA0 +H1ZjcLQ-@T1lM$8IHJDXdk8*9[S),pE*e$II4#r#1YH8NmD!B65#6lp"F6ejcZhe +biaTk!I*Kh$i[`$ZFfcIpMVeZhrRe5YRV@Z[HF3@EQl6XQem+IXMT-r*6hl(*qeP +qeJ9rk2TUhYpXQjcS68d1[*ripqCRRA0qIZTjk6BcjKf2Y#TUjQ0ARBR,FN(YQAV +&BM&%0281jYAQE+c[[VSh,SXAS&k0bq)&H!HIT61p4Fi2mfVHSFXMpprk1$QUDU1 +B4J)R#Me0!2BbBAlQM26)6@dqdIQ`Y[[LCl`TqlNDQrk-K[%#[11NjI&TVIcr++q +S8Rp&cq,d9p&#k9I33RI@4d%,h9Q[rS"H-D1&p[%($(aQ@0[EJE-VVri*jjT200A +rjp@mBqFNq9h[Q'RabKhDr+h(DrVEUh6m5MeHqcl28SrABfTle1P-ARd%AGkk9fc +Ij`(T&DX6b(Gla5DCfKI3+aDZ&9ql*0pm3VPl+T5T&i!jr3T-YHZEh96-rq(TH&` +Qj8ar(J)[aJmd+@IIjeQDP,2ALkNpa%X`F@Il*a0hYRrqVBNl-qhrZ5R0+[VrpbJ +irmBFJHmSVrSl[UHmQJQj9AR0[--l-4pKVr+U2F3[-'I"[Z1m2AA')r4-8LX+TZl +a!bJ#m`k[a0b(F21JMR3cL2hpKmf$&MfjHC!!MqfEh%6*EKlNHpY%b6HhHC!!E!k +bAm@aHC!!hYPiGNDQpr4EZk!!q9hVGV[ADphVlHjp+`AJMCYaX*pUYSA@h"FU%&Y +SHGliS,+&PSIYblE3fJp!f$V+RlDP@ZF,'BSYY+EQ+AP22leePYipkp54S0adCIX +XphRAYp!k+4[UdV%qaDVE,EcSLK46IC[AfYR$0&[[icHZhVV6M$)mfN+V0jFEhfP +'EETT#[%hj$EG[#ahb8%c'qr`I'fKPI&dG3ZY)NrlcEI3mL)SGcr3KJ,6bTe2P,Z +ki1"P63#8ZepU#kepGEpIbl#&eZEU,EK+Nbj8j5Zi8&pHfh!I[3$[b,E3bYj4hED +Tk)SCep!,N!!2irCj!Gi4E+%epBkpETrC[1M02GN@@ZV#h(I"p4CDDZh'QVFqGqh +G0heqG@lGL8BQHJZYK9ALRjblpXljZE8rjEjSmk*Q2RE9@ED&9[D1kKCDaAGN@fM +GN3rMXRJ"kY@i,&k!Gr!P@fMGN9IcMRJ,V6[HXCpSXi9@#fR48eR0cr[b,@jTljl +IdH+hc'rVEq1iXE1p[k@j5J&iB6M!%r2jCeM!AmZ,2V[[B1B2m[RRP*-qmaH4JJC +p0"cKje@DrJc5p2pT)CqNP[EaZK5j8mlh['1m5P,Id3[HIJ3V@ckCZ1,mPD1Bjpp +ZF4GYMe2(FaJPlS,YFIFjKL8!j(fY$f4a,er+!#&jAa6AYjJ@8(PI&$IR-!jcDc& +cX9Xf[XBHE10DhXKKb-AGD0-+@T52EfIhV9`#eUVlk(5paX8pqe0JHK8S%rG0$LH +jZ([rb4iXlkC2pHiZlNSEV-#@a-3aH96ZZriVB*fU'jUb&lZi*3H$X8mack3M@[, +a&GYjS%pjHQD@[hXH#bBcqY6EX[ZZ[KGX`-@ehTEPla0hJ#h6IESZA6iZHKb-'Z( +G0#p,rUkq#QaBF6GNphhm&M"T,'el*-[I*BD0ZELZ6eTZ,U`RaJG5BhbA`arYmbV +eG%j2'0HpN!#$MYX5EBqEBqmqN!$li)N`VX9U3[*pejq!(D1mXZ@,e-IEhJ0'[I1 +1"GPpChd#l&JAG`rV&jIkH,jaK&PG@0rhJ5cI(lS5E,AULIG)IEc+FL*cH9U0hq5 +qcli6E*hLhT(9aaZXdjXmNkI4,"qIlJ3lbm80fp4FbGq#G@!rT6U$Eq@qDeV"(U, +m[5I,hcEMPBIU(F4*2VUY[Xp@hA#3!2ce@4e,*rF)fffjqjjNr#'GiL-rb2,h*EJ +fTq&H9hf$1PjIU3r[i114Ar('ZeCU#E84a3hb(V@kTbZTHrQ$V)BZGA&,M#G%"d6 +hGIf#`bZSHcVI`lTIbVYcbm3V`VPmem#M!Dq%FGh[RAK(&(IN"+q%F6f[jh#dH#L +-'lQa8CHYFbm#)fr8'H0-T5j[-1kleX8pd1k`GGPkLR%6fN&rpcFp)(Af6Y-FVh& +aiarJX,'5l15jZjNHF(&4RY!ZMPrEXlLAd+5Ihdpe%-Eef2[HUMS)ihU0ee`GK(( +,dA1jZDU$-'l#*PqlI)4a5daRG+9jlCMk8@SGAD1r'p%ahEpfFIG#NkLT,Zkklh( +pr$3ZIr9Ma#ebFCp$jaejNEZ[l4A%(H6L"Vi&PRIhIH-8-,35lriUKm2FIDGpN6K +pTq&,H0$@CFGKD$Ee5Yf(9P[JiZkhJEK$AGb+3m(DhIXqmeN`2HRGm0"mU`0lhq& +h%EG%FHb5B0q4Ir`KiVTFh1'@ibqiqp4R`25lM*j!4dQqRiIf9DC2NA@Fapep$rX +FF6dZlJCQG,3jRXK[SFl8Ur31G(1ELhYh,h'L#jIGD9aZhh%kR+'N#eI#alRC,Zl +i!iJ6A6Ki-SF$A&cR'F5*,Zbb1-[hqIY[*djdBCG0DT!!HMUC@PI'$r!%ifrHlq, +QSF28DehF%Ge@3rBG(riDQ1M))ffHifYGh&[3B8SkXXX'9pJkb$q'9MEk5"rIm5" +dD1ZQSr0X!Tbq4!F)hh6!LmVT-'8jF(ac00T*14fQTP'lMQmq$cFU0iG)YIiSijY +HhU#Ncqk'(R0mmd@cU@9hL0kh0[JQr`!k6jPf)aAk(Diq9U,9P'NPH*Im#YqXXcc +*&-B6m$Q%EcS1J918jYreEXcijYk@l`R&`EIbMP8I)qjNk3Ni`2*0rL2'SlVZc(f +2crMQdqKATH%GieXb[PQ%PP1@6rfp(Ai4[ZNidAK)[m[`$IN3[[N'QP2e53paVq@ +Er+eS(GA[iSkb1KDq1GIi!bm&R85mm-f+$a%(cj!!*rMBmNh(kXZ)1edklpPC2Dd +R"`TG`(IA4aYmNlmHEDI3!R`2X(S5(I"!0+[a3j!!ZZKEbcFGCE5('YICm*I8`4( +i(%SkHq+HM'm@QMi3R6eSHY(b6Ilf,a&RGEDb)AM#0rRISl'8dpRUA2)PI00a#$@ +Xh,"YG3(qK2"0rTYI)%$'BDmhlK'qqG`[l@%fEXLdZr$0ZfmJ6R4jRqNPUBrchbA +I!cj'r`K2('Bk5I6k"[5dijZ6H*25*2aaibIKQc'i8QRHk[!(XhFFJrC9QY"c#RV +&mFhVb+Q5IcImmBa[[[CYiQ5bbp)2Claq,Ck0dPB!Tl&'Qp-hhrSYFH)EYTh6i*Z +13dfhb85a%dhhL,ijbh)TIQ+[k@c,0rR,,+rS9I3pHX2b6Ai62Tc5c+2KUc0pmdV +HUNkA6X)lNAUkZp@Cq*ZRQNBA[[NNRU$5**ihh*IacIRN9'R`cK[KB+G[[[PpiQ5 +5d)QACRA`((`1KHELHbYFi[KQ,MP9aY[kq#kmY*EpdVMF!A#Y@ZRL2[BkI*)MA0` +TF+L5Mc*NRShPTe`!KbNhe%hG!NqiGkc%%e*S#Z,HLejelhMS&343Gk3)IT&hh0I +d%ek@2Vi2IH,Hm5UV!mdja(d!2mqpil2fMXY86ar-hR'9m3Hq)[a+r2`+Xb%,hJ( +IQma2q95PZ'0qTlS9hD#FAj@l$Up#R5HGXC!!`bBApfSdZRUhLpZ)&XpGkZ+10Cf +("kL2lm&6890Fh0IH!+Ec4Yb(M-2NI9Y0cehZhV'*ZV6[ZfcX%E`$GD',ZplU80j +hlFh%i5rTiiIa4GclPTM1`kI9airJiERhhGGbq5cTHh`&bGp4q,,+H%`I2iL(iGk +h$8fThZrL2QUEDFMl6[S0'2SSphm!G`)!!!%!N!0AHJ!!9RS!!!*kEf`l$3N*CR- +ZF'&b583J25!UC'Pb1`d*#8jKE@9$Eh!50#i`)&0PE'BY4AKdFQ&MG'pbFJ)!N!0 +"8&"-2j!%!!""8&"-2j!%)!$rN!3!N"+Xp[d9!*!'@I3*#3d*#5TfEf`J25"QFbj +f8Q9Q6R9Y1`d*#5TNDA)J25"QFbj`BA**4$X0#3P1B@eP3fp`H5KQFbjZB@eP,'j +KE@8T1`d*#3d*#5ThBA0'EfaNCA*"E'PKFb!p)'Pc4QpXC'9b1`d*#3d*#A*PG(9 +bEL"bCA0eE(3l$3Pp$3N0#5Th!!!#L%&%3e)$!!-S$9d,iCE!0Qd2b6Z[IH9Ehp5 +Ue9CEkEAkC*9FPqJ9+UeD(CpNDpA*@5)i3-Q)R'5k55HY1-"hcYF,r!1f#E,M5[U +MC@C+a!jlH+aC@B3V[%icXLX@fR,DNr0`SMITBMNC8cDC6Z*'D,'pk"KZJpfp@@Y +N0fce%Me#6X-#1je1j30JAcZ3!!lQ+MM%ppFL13@,l3`kNiq#Xra)29V1aZ2p8$e +-6X$$l9JkMKI#GRp!ljGYH)IYT+em*paYXfN1h`AcrADp66EM8cD0CR)dR2%&qSc +-`1RqK$iT8r%HQd[cZ"kHY4l823UZ0RLjRL2VFBIRp%(CJ[IC4YV%pd*IreFIPcl +iY(@PE[`Sl'hl8'rq(IEcAE4D$X)KIUZ@bRMmhLE5"2iFIVC40*TrJM&HU6r)*&a +MlDNGr`LGr#[p@MVJ5&qYUk3cVV@a0)lA34GE5X[i1hM(rpDrj$emhrr6A[)FrQm +Id,[m$hcUYIUPI),If"+DaGp#2rZB2Z-'k1NVp3[TL1IC#r3+A`5[qX9kSEb-Jra +'[88'i`hf%Ah)em-)[dU[PQ&iV8fKSA`Gh'3$D6KI!rhpCLf6!ILDAk+AbZYiZEe +*Er%9F,kp5-rcZI#5Ak#Ab4Y4q[3RrF'2`GYqT6iN[q#[rV!q)VmK'T-`3"4Cj"V +@eLC8PNBMkcLAbb96p2B`aCDaP&9&U@-[5hXk6QI5UA49HN(DD[*0fCDk4QmZA"b +hC9*QKDeY8Bq9e54!3"2#XdXQSE`ZSEJ`S5P3&#J)Y)Ab`R#S$SIkd&B5D'e1lV3 +f*HZ5PSJk56D-!bP*#TP!2[bb3,EL8QK,"6+"jSS2"5`IY[4X3MD3!)N6iRa#69) +'!*!$,d&%3e)$!!"@$8X$RHJ'ApX&YA!,!-!YrkSA!!$3@iYDH!'Kj[m$@)8Habq +AC[SF!*!'*lp"4%05!`!rmJp9$AC'!4!aiqGMIjYcMV()-TE9'U0IM"@MlZFhK%X +0M$Q(cV&kM"'6f&`D[K#b%A#b!`R2br85Na)aK#$0iD9FNLE'pSA*#4a,Z66Aaq2 +4A%Sje-FeP%XTKkD%BiLe3#RJ!((Ipr[[lr[QCJkplE[hrI`('k5R+%%3*%%52*% +"I!+Dpk1@k$81XCK2KMmBkh)ImdEZFT0$AjL%ESQ#Q[A"f+5lUd1X8%03`%TJXZ0 +G8#P3K%U%iPa9heDKjeeZFCT8f%RNL[a%6Xl(Kq@H#NEkblD+H`kJd#!9&R6m19E +#N!"@#`8L1LLBQkT*bA,EbTI8INfUkU#1afYrX(Z[U%d8m(MZKZ%5'MEBDhm3C0i +JK)p5BU8akQ"S@![63Pml6kiTHVpAihr2j32Lf+fkG@ZlZ2qbbrfRcfYiFSC,bG5 +Y8d[GbakZ`C*!PqQbISC,9cUh[kq,Qp3XJ@&0&dqqhqhq(hIamEjq6RGIeV6GIBq +pj1,qPhCcdd[G20RajrS$`9R3X$UYkqliY"IALYEQ6KjA1h*[iHHed5N0*R[[V!i +NhcQ&U81EjPqfE9TdZ9HB2YR2ihfK63fRECZq21eb,hd!JqT8iV&%8@1[1[8Q"(U +mB&C#-Z4L!dNfeDUT$R9UF**EDh0$am8D%VjcI3Bl(HC)AEh(&bT[R-U`MaT@Jph +SYKeETNk06r)iPN6GqD*Sf))1(1bLli`H9iDUf$lE2P[YrafP#h@UQh(Y)eAQU!L +"dYE4-rA'5f-6,@jF-Qe%FM+L3VJ20NjCD2[LY!M#e4HMHX0pp*'1e"CVaQ'`l%a +NBCU5(l-J1fM"hCpL`8$3JXIql3+aM&$4Hci))BmbK)3b8IXe[G)kX8DXmGE#9&M ++5leI#B-maQ2H"K3Xh1,p5L*fe2eSD8VFMHJJ,'U-N9kHY$IM&A@14GXKc1b`Hlk +a#dE#'"h5B*,%,2(+4)Bb"DC5Ha+[)UFl$+3fh58MPq2TKVX`,*Ffh(9Z5fmH6'* +kV36NJGU[XHe(eBR)*"IjkhBV*CH*BS&Sc6Z#@4$*j$*aR`5JE-J6C6Ipj21m0$X +JhJ(&3pi1JdT0KrXQ%UFk2KPJ`VB,"cZ,'"TZmZ(lPR9cGr$a4m0KCXk*TNYKFSp +YHhhhRQ0,4l28MU&5Yd'*'I5aqTrc4RhL6FjhV[TCfdHr%IGq0&dXBm`2Mikc4%, ++@0*N,QQb[)%-qeJ+-%*D`qVDc`fVFC523&LG+9bj#Y#X0$9Ui43UG*CT1*UeZ$- +F$A'rYYPZ6BHdUY$*Y4Tq)8-aGJ&aY'VER[)M13EffUT"Na`H$cDX3T@*GS,X08Z +"CB0,'qRUekSEZVA*YMhDbLkheN3,(9Yc&$4Q-14,NpdlLXR5TB0q3RQIrN+'IDR +`b5[G0QfPm0'CPb*#")P`%e"+4ZV'kRakDTVYa6dZ0+%Prkm&#-&!I"N-DG'NMce +bMIHiTM)8,Si3q[-$J5YPmE@i8MYGLDH6Jc$mLKi'SNEij!B')[YiVD9UbRj-DG0 +1[I'i'*K'!T*0Y$1`(BE&`Z(N'&8F"N6`Z(-S%G,''V#KcX`0"D*)KeKq*2GM*E+ +K9rK`Q(+8`"Df2d,Ej0`Af*!!0%-d""9Lh'Vi%L%'#LqX9cXFhmY4*SpAS4&M)`E ++)JL)i2AD"&H4I)(XkdYI1mk(IPL!6"35#,a@i(MZe8,H"d,$!8*94Z-S*BVF#+$ +U[H0)p#,f2X1rJ$J5[Le6M#a%jGIcYS$XYr,iXcbdKC(G3Q$fUGmKST5B)N(k2K) +L(V39,*!!!15+8"J+5V)4'G%bL'$'UYdM6b%m8d5NaGX18lL%KTpj[R"peE$L++d +DlUXDpMRmUH(c!4,V(899``%5BijNeA#!4"5Ek8#QP,C5hSp-d"F@e2PQ3jiX'qD +,qILjGK$!Liqdl4Ah)l"e`f'%dRF5Ri@14)kbKYZ6L9))T%24-2H,C441`DF(`TL +d-03V[S2N*jXYf4TBBMN+BHf'I'!b,*l%0$(mH0L56,$k(DU3!0%X,G3*FKi3'3Y +eLhfXH`X4!ZReSrjd*&f+T!hCc,b%e"8,AcdFFXZ($0qUr8TU),,5NA%NbL0eeYP +pif-VZ+Af+erhKBa,h(,6B%rV$#2G(GkR$akADkj4Q'm00Aid1*MPE@CS`#dU)B% +MfRKL9"rjGpSdTkGcXpSrh,rlMcbZ3rM#[Icq1h-I8)cYQeAAX)Z'ep,`RqlrHXB +$1-bMIrd04*+P40V9dqmDI19Qe$XqG&b0fa&98hY#%r*H3eMH!pBbA*JI[-RpFS8 +b1T3Rm[kM!mQ%Y5&2VR!f2iY`XlcRi"PZN[FVaKk`$i"Gl8q%j6)NSrj,XhdJBqq +NL3M-`E#mAjV)3arTX,bhkVLF3)"Q&8-0mq8$GM@5ar@bA&L[jXQ(3&"f,lXrZr( +NX(ji[V`A8mRi16V`ZHT#je%f'4HhTG#pThGPRPacLF[,-F@Ke"3e6fSZePkQT*Q +)jMk&N!#XP$c*9`MCd*!!NY(FBc"5`c`)0GZbPa)STi*0K@%EQaHR`18J"F$U,1q +Yj3MD%YV'R(d4&`*2fha'CjJYeSN)I&NDf2,@l5jAeR+@H"9KLbGad1mj6#'p21q +J5clY"[$8i&X!3R0,fekj"JKd!S9(@(+I*hpLKb#T0"ZfJ$8A@EI)qi2-FF0mq33 +Y*mEFMk('RY#0'lB!kHb'e835a#CUrkSC*p5Vr@S`UrBd8kSQ*MV!R-X#[jbIa-F +rq)kM35QQJ1l3fGXFE'MQRilk2V6I&$iL03Xrc,ZJ@LN`j25icj!!bJ0NAS6NcUS +ZiRiS'4Ui"85MIXkG-2A'r(-JVXF`e(Kp50pIaZ11q*`5CGjfZVBkYU,V0#P2&!l +f&6Rp!2AbB,,f0!AS@`m%+3FHk9KM`V(bq$K,[19Y8R*mQTRf&l&-h)"AXcJ23h% +44bUHTLc,$!N3mk"dH+9&FD"l+eZLJZdL%TK1PX#%m4QR")BP+pP1[d08aX!XN!# +IcdLUM'3m-9pX##,C(#VijlEGCm@5N!"02#V'S#l24!EH54Rpr$2f2+",#EE3Md) +P3UlImhS-pb&6MR6hT(@f5lHIS!j#*pT%m9"#YpRSp@#k'`"YP%hHb5l9I4$0QTK +!Pj'3!)4e&50"baHc$IHaaF@%Q!k6pbNb,80KjjCZTXUr4U!m9rM-HIU%`m8%a') +N&46J&&Y5)!QUD!Kr`R!bj[@`j8m4HI`$&F@j#9e@dZY*63k`XL&FhA#3!),AR"2 +qFB$`q6m4N!#C3B$-C@3`P0`D!%)!d(RqL8)"%!+`DYKfr[QdV["5(SNX-6VXK#k +q)m8!FQMk)-lNPeE394+kTlmPG@)5NcmeVI!aPF%%RjBQRj9"M)H%$fRc#p0C%)$ +c,BQfLL-m0j6l[0Y@2HE0mmVN$##+kRMZYPTVED+f"HVPIr&1j+l*A@1`-j%'dir +8*9YIc"0r8Gj)+1%mVkSBVKLTbh)JJdD"L"EaGdi@,RGBAY5A[dX(k-blG8jLmff +AEGC)AH[bK'iVhk![hi@I-P+(b[FDE18l0a6`52QqNESM2Q6hJS!5YF'QM4fZJ!U +Q(LkMB6M$',[e'r6DT-'QM6#Q,'a@5$X'pBf!G!cU*TYVIiGX3pY4+!hjDhm(Yje +EdQHdpRFrq&Yh&TCj62U5%V[TQL9!''Lm#kTIc'(XdQZ0KXAP0EiXTj@TD3R@$&Z +"2&Tlj-dDEM%C(5Di5m#4JlF,kKUIj*b5#--@8ZSTF8MVB&Q!FKTe)V'49$Va"T* +mZ&)B`Z,AKY$KbLk%BEE'S'lD$PH3!-*jZ%bViVU4&p(%D6I$D(!%kZBK8MVAjIV +d!aP1AUT9EFHmEpUd8AkGLQd9@jB3f$L@9SH(3'G+TQN38kmVp1QRD0%l)h9KE'f +XPCC%ER,,PKCR+j3i*hjZN[VVYXN3K*i+Al-J'B9"q4TR`rPEBaLZMj!!NPmjNI' +m!9F*D5ZK*$r8Z!cCl,F1IfiBcE%IcN@f6@iFma`NKG8qr85'diUK%@QSJSEXrQp +L-5ArCkGke%'Gb8)L0E'3!"Jp2%mJ$M-E('YH(Mr4YJ#,#Z11F4L9jlbS"cKj0)f +f%Nh"`SQ-)%+F#1hHFeJ@&"pe+LHFJ-&"-(3Np@)2`NF($KE`E@5@`Bq4b#!,fJl +)K2DeCEl3!GNCTd-rA!Qe,FpY1h`%2hX(3j[3J+[c-29#A!I*r(!%Qh6N-,Nq[8r +$)h8[9!0ajM#4!8Z#d60L-h4V%fKZ-qdH*X4K'hqp8jJ'`KJNi0Ym68JElS5iN!$ +2frhNR)1IDA$QmU@,e*!!20#i@*jUI1`X'8`AFpqd%J`U',``Y`4ESXS@Cc-fe8" +Eh6`'iHFc#'0*fHHX'f4UAR-FJr0pa8Kd6bqmH'ZYkMrj"`5YLN$-N!$Q`P%QFe, +('K!fr9Mmkli!S*DhI,h1YqLkkG$K)i1K`h$kDVh'",@i8fhDmFZEK+ba3e)hYic +4J3P#P#1CSa#`X6N#@PT-,(X$aCXI6d2TH0S4AVcaHZh`50hQkHN3LNqKDcU&BA! +NR$IkN!"KS#)XmIADNiD3!&P@M-,'%1UU,a2!CRNL!iX+KR&3Xh"3XdT#EH)RdQ& +jQS*U,#-3P625SH(i)f90@9km,&eMY'R(NB[XNM'Kfak9%'HE`p')F!pY%'XqJTE +%YT'5#H6l"0HX(#!aG46Al-6R!XU5'flbkpSSblJir+j1GY8)3jUl)h9l)aFb)+! +%LE5P6*!!JQ1$XG8BcB)"*KTTKhKAk&cMbcUe$80qChXNbE2XSc$**,P&BMMMp2M +DZH98$-0qER(5F"B+ULpNVT%QK!PhMqj#"LAEKbYIe*YV*$2,Rf2,#!"I[ZR1UH' +%MTrm[Q[BMF+(UedrAf+2NBNk-ClHQRYb%!DpN9I95p6JFFZ`@dQ@VYX-MdKma[E +TcqS0!amY%@ZfVB!4-0mdrj8#8BBE&4PiYYS02TMPmP3-LYc%TNUm3SkPq0JmI'B +)(bCZ8PS0cl!P*M$(l&%-qc'F4+%84T0RZ$r5adf@G&imeQG*pqMAkAN@)pBFlH2 +qcF2fj$SL'ShN$iUQ1eH&ckfa2Y-Bh%6rfcBGpcHSprd($2&YVl6aZ+r2p2Y6pGB +qbdEFB8F%aM"R()$2AAF64rId+rQQ''rfjCGUEGfBZKR,G3El4eLZI[TQIRb!ecZ +L1BTck-fEI$hhj'3j-eqibGrLL4F+qRY@IBHh1LcS6VjbNhYB+15+ZQViq)9205[ ++,Skm5eZY-QI$eDid1ARah-AIGrhXchB)+ccb`@*bpKc19NCKBPq6d*9p0)cKHDI +Y5GpTZeA4lUlQkUmDBI`RYK)$JZNm0[+NHK`CT"(X2FVHfdqF1irf5E%ppra0&'k +GrkrREk13!1diG`N44Ik*dr'ULX*lrh4,T8,&UDZh8,"N2[Gbi5N8$$BE-9KZRA[ +14SAX3T5S`+8r)-iM`pKLKcrb$(m9,X$@b$0)mD*1IipH5FEk)Mqr8)+-S+!4Ka- +LL[HL!@"EKJmI0Y&[5G[lT%eEVhjk&1EjBeX5h`Ul)rVaHVXRSKqXYmI`kS2j86r +H"mGD##HB0Zi!1MK+#@e"LJXERGHDd0&4$V'Mr*I5e5kKX9L3!1e-BJPANM3!Gd3 +ZcaD3!)&lKYh@HMZACmhM6Zh[rd10SR-H3qCNPVmSZK@K))BHRLES%5AdL%hb,," +I&&$4jU'V4Ai@`Bcf#&kP0[pC[3#JV!3$jXd!B(f@k)*XHjbF&VR(Qk'1JfhY6`i +")FY&KLq43h'XhUf2rG#(%)bp'DqNhImMR4,,e5N4iI0Y(3*%ZMM!'3GDU-I*r@@ +N%FV*E+!Y%!D1SA!YiR&#Q+N-eV9-rI3&U0EFbKJmZ4q$SARN5D"V"S%63qM*kFr +j@$'pr6%5am#3!"&$aU#KT#-V4e(mMUbhP5D-F6$'Q`a&-"5K)8T++*3%%A%[%2M +6N8BFaMJ3)fQkidcQ2KJ'%cPjKe2-%l82i5$m`pP)LU,K$b9Re%qCZ[!8#@beZFJ +@%F2VkVKp''lT%iPk"$5MGXmmr98%CFqffC1aqRHA+GTBV%HI'djP'aa$5cS,!NC +-I2c"$&,((C`F5N8MM1iJ#!&X(h2(K)#"d+BaM2!j,4D1pkklYhrUhNml5%N8@d) +N&MjqN!$F4ID&5(#T#5BT0a`mcUh5iMJY2KYBA*c1`N+2q!i@@X@&M04TU*1jca+ +j$eMJ-SH4+jE)[F-!M!,!p`"B23"l4#)H)q+I6[52p,L#L"XCm4JM(K')`qM8P1K +E!P'SJ'qVIa(q3G`64,ajT2%"ZV4VeBMV8q(D%[Pp),HI!GVN)-f2-k)a!,U,$T3 +18MV3#SRiNL$LS(eIGaAa#R)#V!qc`%3F"XCQ'&@XNAbdI@rCeP-@qm!&dE@#13* +R#Sl!05X3EUeSq0"ZI$"MN!!C!mIphN`F[4%+F#-6E(F&ZJ4AGlP%-qk-B$K-Rc@ +c0H034U*`r[eI8%`qH6#$`XYJ28)"$&`*$-iVM2f)&0Bi))5[j+2S8S3&0!@Bbc# +8q@#''XPpN!$#e)0mh&qlp1"a!U,f(c6M$K-F%Jii2G)LFQB2XdRQpBdVXF!``1V +T!eK1NmErjYrL%-R&AGc)A"CR"Y$aT,$Yr@GGUcVkHcXHl1I-p0qRdd9QU%Rh-JT +Z9Jp2ZNR*RMMra-ZN[0EQKXG(1kj`YAGS)YqL&2VmpQM-kLkTd#[q@+Dlj2D1jGc +R[fpTYppA#TAL$GfS#V(M-[Im5@"4`T&5[Q2lE$-#E&((TXC5'S!,RU[`6PG(GGq +UACBf&(453EpU&ip"K0aFh8fL9+PBlF+`6qSHN3Sp%Y[6eIF5ffDaHJ$$&k4Z45V +FNYLDUfm5fcUaqJb'mk6ZFe,KGY@89k3T#c"F)R9IP!VCM1@'a(+(QC!!"&0p&Sb +Vj1`GSDDjjh8Nekr$058kV,I#FELRPlS`''[S,Cb"KMiD&*UaEE`MK)XqcJi%(5L +,D')8K(0DQG)-pCRI%04RT4iG1j`80SY')Clb)JTP34aI,T!!K8%BM0lcETYjP[I +lJb%%Q'SDEHBjU*!!mHqDN!"jfRXfmdbB'M#apmE+ii@Gp'(Hllhah['JiH6Cd-B +lbPjdPG%J%X6-L3c@I4(6`-(!dS2"p`'@MKe"039Zl5`X[ZL(%($,&N%FUdjk[mh +#P$RiD2CqQc%P'C-a&EUV!RBk!lD&!6ZEJ2ACc!XZ654XjRRTU#$@E'UKJ[FL#qa +3#"-+qkCUB4#-#kSBIp5%dF3BC`Q$!$P*Nd2GjX,N[XAQKCF+ZQcQcmUAi,@m[!A +(Y!6(Y$5b'+%2MZj%SKI%mbeBBUk$S28@%pd-l9`DA#J-3Ya"SCdVQE4!BJBEXD3 +k%k(bkBdZ'%YQNqX!1F`-3!cJbl"a$"bfb4r$K,)%2p-hf-b9j5eil4-@H,m2BLY +C85XJ#aaJSZrLmR5T(Kb@-)b!B"4J93@5Q""`6qf+VZ2QP8$L([0RH'de,m9,-5r +I8'!aQCIJ)f5HMYHRjKDm$TTRSi1E'r'K0mpL(62a'M(2B4h69*YjTF28H(*5MbS +6bC1&h,4"Epkh$8,)*e-EfXb9kb'XRi"c`!Qpql2(),*[-*42aq"m$#ikdFB'NdN +$0k*M550G&SFb#eIEd&EH`P"PQ)4AZLc3BbrE5*i+lLrUlcUqmEUL"V%FDFT5Zij +BK%-e!*N01"cm,$!X0YIKCq&JU(bRpbjHqlcAT9"ChE2Cl2L0`L&54RdAl05TdQB +'MTB0JXa4bQiNk[PZ`[4#KM'$3fa!"rYBik0LR"LkEQkCCMk+c6Y+VM4NmV'6dGF +BaP#A69i$aU4$l0+EMkTC,)-4!&8Lj1ip-ZpDTD8%bJ*cN!#$c%%%L(BXB"5b$*! +!q-h)5X&N3[&V[$dNDdJ-&qF,dbDbP'faN!"X1G8+HrS,GZXCB`mr&32E'M$0IPb +[c!*,6"+I66fpe+@X&"&K5b'-S+#UZS[Uih3U6Cj5-Mb4FQV8NB!SX")+P[Ur +!iKX&FLJDZ*-NJj`&KU0G5,b-U#,6aD1Q%R)D1"G'&B%i-piXJFT[TF9`1NMDNk0 +ReFh1P55`eJZ11%H`aDqLZY&Lmi&E-F1@3Gh$6e)6AIY2[R&lM$SGHBdIiN!2T$) +m,F)90F5m854Q)0Pb(0JB(9K#RdCie+YJHC`kiIUE2j!!N8UNf-ATD(8q#[V5jQk +$)H'Y"$N,5)P1H5*PEUN+lMmf8[H,*B)CJE+T41i,#TKUT0!e"50(kM)253I13Kq +EbJD3!06+f04IPl&0XkB-416NCRE#%4JQKZT-r#cVkS#"#N(R,TF8d)i5-SJG$"Q +D,Jc$0(88`D2N)+T@aGr[+TKilYc,fPKA`GP98bm!S9)NR[E"2%8U+Z6&*C,4dU6 +Gr'qQLY08mVke)5e$#CQ-`$1!c"GN"ej6mXZ'qk`KqDEli8'Ul"#5E9)K,48+T3) +IY+6!-Ph!346*&-Vbf'%fP8IPPjR4**k18"JrqID&$!6`*C-+NY*T$6EjPPeP6*Q +8c$9eVCKl"FGj,i5CZQSVVM@!+af5c%flYRme`65lp5db"FP*ZP`J'`VC$aR2+Q@ +mERCaLh4aK9em3(jCJa!X,[C-eG925B@`9$JM&5b"UeZZXUYEF29kG[91q@8!Ib( +iqNAHl9cIqH1[rim&)h8("ZMkQi`-KHY6drhL-Ph32-T-CCc2&G"YlYbe2(a$B)R +H0YP%@IE"K'4QaV*Y+-VfGPqrIC+51&PM9`'Q4TZ*E$6,RQQbbFJR5G[2GQTEf5Y +653U$'fbb6eX2CJ13!-dQZi"U0VP$fiIAK&BA3$Yf!$kl"J6Rf$YTBZI3`9j82(# +*+j!!*CG4eSJi`iZC`Pf(Mp1Np#V0U8[Y(#0!!@$%&j+(cM!hM*`fU-JlUi*R-J) +B5El1(Yd!JD@m8['MLC@U1U(D+"b!+$YHXd5-I2ajIrd+2SkTGY,%QrI#I(kekR0 +R*-aI&(I#F*k%N4a&9VeKjl-VH*Jre)-LUf##*!9,CX"i5L'fIa5BGQ[j3k`i)bF +Fk5epp"*9JQbRJAHT+J008H8fNM`UQ5+,dk4BlF686R+-LT8%9dBCTdT59BN*a,S +l!SUEMK#`jHrKjdJ)4bkHa'Y#2#&Xl%&XLhJDVfla-MTFiN*m1-9jH0d5&`J)-D2 +&`hfP-2"(fCDl8S'ICr9LKUr8RSNX0HT-iY9TE`D486LN!h(&q3VX3B3Z6Mr!b@! +Ej#H"9ET1`#!r`V*HGb"4)9-bQFd&jjrUBX'JE"V&G*e-Q5XL8NNZ12"dSH+*YYZ +(+c-8C4R$%Fp4N!#m1H(ZU6@%4&N6NK-!5b0H&`c*-(XhrVL'&35[DmH%jMA3X&C +cbDbG#d*Va1[5iD!CKQQLl[!dAbr#0dQ3!(p"YV#'&Dl-V'P9cm#XKXD9@iaRHmQ +F,B![qi4XCh%(VP38EJpTTqJB!-4*C)immB4@Y`&1qpSa[%jVkhfi$$Zq*,Ij13C ++l4'`,8+Q3M8FV4((N!!")b%GHj38#FU!k&U)$)S$#Kb1$aR)ILXG%V9)jPBLJ*) +0XEhi2lI!H,e0YBRDiBjI03*Gka`ap5pNd%hmK@@qX,J#BLLbh82A#)`H,),!"'F +*XEJ"cLqfT3,*U0D`@$MiJ3a-9@drFl"Eb-*JU"BbEK3C4qNjfrX'!hNpMQ!XB,3 +[KcTbE&rJf'UH9*28P)9,!S-(&J!B)@RMFR[!P#iH*4@L+Pb(N!$q50e[@aRk0GZ +Mj'3#B`#3!*-E!k)c*3qNVUcD`kpSG+89bM"6*&G6dM*cfUJqr,RhI2J'Z4M@rKk +Z"(rSkCh+U(+FcI+1qcjdcJjbcMa%$RF`9V$Q3c2*Vb1TQ8Z"369dl-XZrE%[m,0 +LJfYMYX'fXG9JUmj"H1N+IMLDT8r`8lmY9$l$e#EZX-GJPDhfYr'R%GVHMm4kEfG +)GU#`-[)BjrFFX%8RG9436*0&%,)3L$!0851@KU0$1R5X!8h,LXXA"@CFe@U,MNS +,4V&J)j))bb%JiKNimY'#"4LiFIN2)hADph`ADF'P#ck!P9FYC%RY)CBCh$39$"a +qDBT'harSF#mP`(b&-D2Ke5jKc+dd$CK,*I)r)PCKdd50!$)H2##!FJDSdCN18G- +A[3+8(+2%X%Qc9ScYZ*d+B@h!SCC2DqS8@-U4)$RbS%cMN!"+)-$KXV+M-+R5X6& +Mm$d(`P``3%&B*b4mKT!!N!$#V&fpD*`QY$%KmiG$f[VfV'q$(&#[UTV)50fK'Yr +G2%r%*[j&LbUC`X'8cjh+`*60T'JI@iiNrdF$'D5XmQ@TTJFErdA$iQ-Vk%#1)6X +Fqa+&,mM4M5&QK+QA%D$,&cf,Mhe"TS)Q6S"@##20$A5e1"9rMb*3'+qAb%@[(Lk +#%8-KEiFEB$-pm!$'A,Kdr3J"Y%-MkABq",!5!![9D!29q1N3I$Gj,%p,46K1)jZ +8'`11iD`jY$*MBBf[%)c9QRBHSkQ%#GK$&)CH+N#9arDAb%@`(P1@![RQ5YN16GK +-V`D"VN-V@D0998NH6IblpMK0kmL%fCK9pFLTFLH%JY@-CS(%Qk6JeTS+[C)VbSp +RBikBriD4A#E`F)US[CiF'[*H,5aN,Z-!6h!#dB`(h'5C'8GJ(`-lBb,h#c)(1U8 +VL9pUq%"'X'XY1mL66PBC(qBm90Hh5`fP4(Bq((Zdq+Pfk+K#*BS,m92'R$fd31S +qE`DF+`U%!DT-LF'eE("ZC$kjlSLI1@G&jM1hRqA1*A!5LpKeM(d[@,r,@0Z&!BQ +%L)k$V)-D"Y(1(R4J1!RA"J6aP'E*r+Tp!BBBlKFH+'*1+8NcbE(f'JH)j%Uq#`e +@(6QPS%$KFP59P"F*lL4b$VNDNT-+'BFB'K[46)i+3r,q9Zj,3RL*UNphjQreGh, +D!-Ij2*8pB!3L)j&2EB,6PA)h`HY9"lPel+Tb8jSVZ#Q"6C9!ZTFf`@NP9c6DPT3 +,%0XScYmK9b!NPbHfj`L8qceQbUfXQYV*MQjZP6Y5TH$ZJki1iH,Q#MDG4Q#Nbc- +(-B3pK5BLjH4$KPlKD-`,fC+)F'"Q,hdbB$Tp#01S4ArrQS'B&0b0T%@251aAT'R +2#CFeRi%lmPkqNicNJXU(V6R0R+,)l63ARcNS[)e'ZjL#-*,,f0X`3cB2!pA34,m +IP"NX%(6L3VBK!5MPPN0,i3J6F("1`T8QU54lHTAC$Tj#GMP1e345&9LZm3)%!aZ +q4*8!M`HSm0dTMXI)E0%M@0[&a)S'Z!R'hB@FUKG4*LL%+f%Fj[Tc)CJ$a*-(Tm6 +E!,d!S#Z8C6AFe`[hNRcQ%RI0EN`4h3+95#*k!@5QiHGa,0iABX&@XB+4U`'C&JU +l%KRRHVJp'C(`Q@Kk(16%50hqDXdiN33j9#Ak,Ki9Jkj62!9Jmd3'Ueba,0KP1RA +9dEP0rNqNR*mGr)0K04T)"R+IHS0,0!i*lV9*5P+J20p4ZJ8AkmT)H"aC`)9(R-$ +pSjeIFFfiUaE"fA4)J`F$)I4!4TTkaN*'3d2ML@'pJe8PJ!0-9X#P'C@,9J[*dVr +@898DYS6F38'f+RN#XX,4&5#NT3S3+A-)!l12h-+TfJ-fl*h*l`ceYUjc&b[eQ8E +hBjGG[[km0EjqCj`HL0+!#N#h*JGZV,aaGCqLCPS&"PXV'+b*6S(K,+Y3iG2R*Ep +jr4[6RjMa[eZHMlBE*cmC+PDmhjMZp!pmVUaTAcH+!69qm[0+226KQ`prFVd5MmH +i&%[(@B@5-lePc1'c1Cd&4e#UG$!3MNpNU&NR2rpQb98Z2JQ6qmlYEC*UR2c9k,+ +Uelq2PJ5lU,0!'Djj9Rq1UhpAr`l9YTSk"r&3'ULQb(JNU%A[3&L@QVmX5Zch96A +E3*)$DjXfd%!e`C5`6jG9iA)QVd5RSi,H&@lUj9R*AJ6ik[b'39f-*TK*9@8d+pK +NU&3ZjKPX)J`3BUA2aC4kPm2kpq@VpP520YkBe+H0JcT,Ql!-XYfAh%m$8-jRZQr +UbHeX#!ea""e48YQ)B04+"K0P#X,[8'"a8I8`&ZhqSjkj'q#&cj``((r8)+BjQKA +"a)Z@%d!T`N9IN!$aJ-H$8Bl#5E5!+Y$4Bk"'m3%EFL,iU!l(QKk9HjNe*1lj9fm +*VJc"V()#eirBH,fiblCNE8MFEqVI)G+M&S#SR`rT8D89#"ATjj%mDap-@'*P@5i +Yq`V,P!%D1"ICaiEm0LJRZpCDKi[FIhrdJ9Q,aCfME3T9NZ(LSh$#4q8e0'fS'VH +@UdAZCBpQ0a+,RNMX+b%#C`Q3!,0S)&[keN@k88Pq9%hf&)1Y!T@8,dD+bDR5&LH +P,RQS!)NiA4DZ"bj*`(i,9CjVX&"c5j2``af0hkYI3iqb@"&m'&Vr6hmB4-cCZSD +1C(HTb+iDVlTU(0I[")[H+B"d&5,NY$@kHm6P0R%rBpj4aEa$h*AFcGZDEN[jPIr +[E9NX6[XrfjLKi)hTC"[6fA4MKYki-6eXBkaA1#TkAh@)MGdDIXAbDe[65aekqkH +rP2V2MApVr&YL8Vkf$%GfR8fiP`(m)GKkqK2p+l),Qbql%[f6c*hfG,D5a%IbG(C +LGbFEZTaGGr'5[kii$bjh24F&4%QK$!*p*i04a[mCS8cN)KXbjI1cZjh6&@k1NaJ +V9Z#kILSJG,*lEVCa,VUV&9kcLbh&idCD$b"4HP)`S*0a3Lk!)D1!URJqQMfT9cb +M(ErG)qidR-*MTC!!m+jC)BSp[4-h)VeCIPYPbN3ZK(l*+-3U58H%"f&XfN80pa@ +Hcrrk'b5dlXH,eBjhIq1*ZIm`p*hre&+hffjF@aVT&d@R(pPk&"ArL`SX$mhhp9r ++(-5@$4E4BlRiF,K%DNa1N!$`dY-pm2NA5)5IQ"IX8ZrJ3f"Ufb1rBc992'*UicX +D%kMY*(hm8Ip6I"VBCmP2qM3CK+)D9#9T"f[D*R&aQ0STN!!DbCXXX2`k$a8G2)P +q((mf*6H6qVljh2V$AQkDiBVdalJ0CLPHTr1EA$[%6VmMXR0arU&Tl+%DpfH[1KB +e#89+TXK)D)q4NC!!YUE(*KHX[kXi09EPDV5)FhYBf#`NP6[a'!Lrm,RU506[Fk& +jKfY53PV@9k,K5RX6GP-9ZfPll&89cYmA'#RlU2S%,S,M2a*Fe4M9ijLD0QQ6me( +T$Jri#mim[cbcrFccm1+UM"0$aQQKM%,KQ4dL5`VQMZVleYhQ(UTbM+lp,![p&3Z +fq@'Q')qdmD+VR"rGA@eC2kYYeLpMI14GHMJ!&YmBeIHi2(Mi#+m-$*d9KQB&KU# +QNI#q6%S`QP-2#VVrJ4m@#`mZN!!BLjXQ'9Kd)cpV#6,h9$F$MAAG4ee[)"*8BEq ++Q$+52dL,NpfScS2PZpMb(rbdbf1"6iHPdHmT)8@UTlHZpbUA[lJQ2AVXrZbAlNC +0E2J)QI5&6&4c,kUk(K!H##"Abh2N#M%LkX8#SEUl`Klq"A6B)hp*kX&J9J`"9RI +*)%mE"GA&('1CX"G)rVdRCU8UUV-+VAh#eU0bPT'bprm%SP2P#50c54MiI#9Pf[e +TUZ`LLEFETb2a+4%$&D@4h5Y23%'-GYR%LZTjAEh9#j%JUG8V0pJfCPF[-LbZIUp +Y6r8R9B)rJYj8A@"T!`(PJf',c%(915"6*i3RBI$+&J3+CY$aerlhVZ0i"*!!6Gb +,kSeiT@2HMdCHp8k-[!S!!!*#!*!$#J#3!h)!N!4#H!T+RFj1ZJ!d6VS!*%*R5(N +!!2rr5'm!"%KA5(J!!5)krpC1Y4!!)'d!E%k3!+Rd)MVrbQF%6V83!%je@Bm[2&T +&8Np#CkQJ*&GCMbmm4%&838*RUD!J9b"3)RJ*##45B!ibf'B+-KTJ!N)C8FRrr,[ +*CZkTSkQM@Bm[2%4548a#CkQJ)&HJ*5"3iN!N$@!'-KM9Y4!!8FMrq+QM6R8JAc) +B0"L`@&I*rrT+3QIq6[!Jr#"I-KJd',#B9mRrqNT#Crj1m#$k)&mb'$3BX%*Z#T! +!3@d'd%""m!!#-""RrNl`!!!J,`!%,d%!"#)[!!J[A`!%51Fm!#3!*J&)3X6$+!! +U!8K&b-A84%K#N!2!`G##60m!2#)I6R8J,`!%,d%!"#)[!!J[A`!%51Fa!%kk!*a +-h`#-)Kp1G5![!!3[33!%)Lm!##pI!!4)jc%!6VS!I#!"60m!M#)I6R8J,`!%,d% +!"#)[!!J[A`!%51Fa!%kk!#a-h`#-)Kp1G5![!!3[33!%)Lm!##pI!!4)jc%!6VS +!$#!"60m!M#)I6R9+J'SF5S&U$%5!4)&1ZJ!J4)&1G85!6VS!&N5!4)&1G8U"DJT +%J8kk!!C%J%je,M`!!2rrXS"M"L)!F!"1GE#(BJb!`8K!-J"#3%K!6R@bKf)D,J" +#3%K!J-&)3%K(2J")4il"-!G)4c)(6R8N!#B"iSMLLE+(B[L!`F#(-J2#`#i$5%I +1`%K(dSGP#*+#BJ4%J8je8d"Jj%je!*!$A!#3!i!!!!aB!*!$B!#3!b!!!$mm!!1 +Tm%&%3e)$!!"B$9-$R1S'YiZ&ElRP&ZjU#kJ&,-#Y&VE`,r!YGe&Vp9i!!4lq2r+ +65hNQLaM%"%SP-R')#kJ$EAISXa!"!*!$#PM!!!`!N!--!*!&I!!"!*!&D3"M!(d +!R`3#6dX!N!Fp!'!!miKF9'KPFQ8JDA-JEQpd)'9ZEh9RD#"bEfpY)'pZ)0*H-0- +JG'mJBfpZG'PZG@8J9@j6G(9QCQPZCbiJ)%&Z)'&NC'PdD@pZB@`JAM%JBRPdCA- +JBA*P)'jPC@4PC#i!N!05!!%!N!9Y!'B!J3#L"!*25`#3"33!5!"R!31)-P0[FR* +j,#"LGA3JB5"NDA0V)(*PE'&dC@3JCA*bEh)J+&i`+5"SBA-JEf0MGA*bC@3Z!*! +$6!!#!*!&-3"R!%8!V33%8A9TG!#3"3S!8!!F!4#)'P9Z8h4eCQCTEQFJGf&c)(0 +eBf0PFh0QG@`K!*!&#!!1!#J!,U!#!!%!N!0q!!%!N!96!(-!C`#["!*25`#3"33 +!53"&!5k)A8&Z)'PdC@dJGf&c)'0[EA"bCA0cC@3JGfPdD#"K)'ePG'K[C#"dD'& +d)(4SDA-JGQ9bFfP[EL"[CL"dD'8JFf9XCLePH(4bB@0dEh)JC'pPFb"ZEh3JD'& +ZC'aP,J#3"&S!!3#3"9d!F!"a!+`%!Np,!*!(5J"9!41)1P0[FR*j,L!J5@jcG'& +XE'&dD@pZ)'0KEL"[EQaj)'*P)("PFQC[FQePC#"[EL")4P-JGQpXG@ePFbi!N!0 +Z!!%!N!9S!(S!I!#f"!*25`#3"dJ!AJ%PL%j6EfeP)'PdC@ec)(GPFQ8JFfYTF(" +PC#"LC@0KGA0P)(4SCANJBA*P)'j[G#"cGA"`Eh*dC@3JBRNJG'KTFb"cC@aQ,@9 +iG(*KBh4[FLi!N!0D!!%!N!9G!(!!F3#X"!*25`#3"dS!93%6L$T8D'8JCQPXC5$ +5AM$6)'eKH5"LC5"NB@eKCf9N,L!J8'aPBA0P)(9cC5"TG#"hDA4S)'0KGA4TEfi +Z!*!$+!!"!*!&c!#1!1!!dJ3)3fpZG'PZG@8!N!8%!!3!``&L`!)$k!#3!``!+!! +S!,B"(!3"998!N!--!#!!#!#L!4`!JP99!*!$$!"L!*)!m!'B!)9993#3!``!+!! +S!(8"2!#(998!N!--!%B!TJ#k!GB!KP99!*!$$!!J!!J!SJ%F!)"993#3!``!+!! +S!+i"6J#e998!N!-2!!)%)'pQ)!FJDA4PEA-Z!*!$-33!J!#3!`-d,M!Q0#i`,#! +!U5!a16N`,6Nf,#""E'&NC'PZ)&0jFh4PEA-X)%PZBbi!N!-D"!#!!*!$!c3Z-!p +6G(9QCNPd)&0&35!d,M!!N!--#e9Z8h4eCQBJBA-k!*!$#!FJCQpXC'9b!!!%-d& +%3e)$!!Ch$9803b)5%HCHEK"N,4P%D[*%*!X3@DZQ*LHh2BZ-i(ERb%Qh-e2bQAp +h[qrELM`EhmbhY8#5eFlXbH4*f,ilNa'5j9ENLDc)laq42j1IbEcCeRB454B6XMF +Yb5)S31)(rdjCKT&84$LS#cYiiLGf)5c5J1e3aD%@GK*H(mq1D3bR6lpC+R)6)mR +Y[@4[%r6@hmf'R%[)+8FIZEr5&V,ejRAjaL3*5bPTf0kN&a@6NPGC,a$mi-RK`KF +HI%V$*QM4BN6+hFfqTX2b,*5j55k)jb(*(2h&i)XDJqim&Fee*9$cG*JIMZ6*i#S +GjViNe#Xq)@+3!*h[`af,NK")heX@$ED[(5XhPA-`LA1fRcbNbSE0RBqrZ,m'l)Y +Vm[NQUD!A@)"Ck2@aqNDT+'b%UlJKdF4%D4j'8D8QKLJRXm5(8JQR40@4X8N6+L, +JmB-'82KL!,85ECR3J#8d%@TbLdfC`eLTT"qR$+aeU)[Di0J#T6DMFe4B`aLflrd +NJ-)Z!m3jBTLiESAmcl%RFHbLl9fAJNh,JDRU`pD$+ZFdM(GdeX@!G"Z$#pDfYBX +aSNc2)HBbh"2bA1lEQ20L2d(0f,[Q&#I)A'ZQ2(KBb@@qIDUNT-rSkShVZbh3b(2 +j4EE#Hi"%aD`5%YXK48JPi!JpjfESS#*')G-lpU&Zma9#N6ZP%AF+hP-PhjBJ16h +DlDNc*qDmR%1jlK@TB-cEfPh+FK[jplG1#3aZ,GrmP`rLNbG@J3Mpl)N"@`NmD#E +1Ye,'NV,SJ&0-'N"4lGDH(L15jV)kGQArf*KLjXEBBNT+ilSFVqIQ2L6S6,2!-qd +r5A,SM5rZ8%e+qrrFIEa)TAK+(ePf3"KU9mH96KeiJM&J8-CCH2(UplHlTpQ8&pI +V!mfS&!eYS22Y"H59K,,GX(5'$iDUN!!D1!1%4[Dm+R'd+EK+q$Ll6eBP[SHY1Jm +&+*f0f9@c1LPl$U`!LB0EA#,N"l-#h9`&eEjM4RfXIVf`GZf$Y!rlJeGII3NEUUX +[JA!5-Xp,Z*!!K9k#PFalib#6LH[)jR!53fYB8Y@+#cA4e!fZkRA5eL(bC5reTB0 +Nq+`1N!#ULm1*SfpKL@KA(V8&MK(J'6fDpK)ZJ-$X$'lTUd'pQXb1L-Hl60lqhlJ +2YkIbbBfVNkBAI5lbXT26-1CCQI@i%dADZX@J'V("P$45$f-VG@mlh@!+D@pipZK +GMr,CUC9ZcXXMqF'dI'XXGU%XA8IkajQ+#fPh$2Aj@FVRjqVrINKT#[0r9hl1a'B +ar[EhjGLF9@FZ6fPZ&[UQ90aX9RVbi))fT0[hNY60m9Pip++5qIc2PqqBK`c8pE9 +GaLXRlhFHm(""&mY9YB2CTqfiUkZ$G385RX(1bd[ERX3qk'4JZaVPpDGj!R%'T[" +3[!PH+Gc$PhJ`"hHqIVA3@F0Eeq31!!!",d&%3e)$!!&Z$98,Sa,3(`,[MX0G"i1 +VS')4Q(4+"CeP@S'Clmb(SM82Ip(I(*K(D4A2I'$q&[0K9K8c&"2&r$@V5EkTU*K +9439&phSP6c(*!"1aC5A+J0LHE33'eM"R5Q-Af($455FbMP!J*%5L5"VfFJe#ABT +bVcRA4B@a96dp+RHhD`rFF&F@))QYk$RCaCMM3iY@r'%I9"NNRi3H&+6Nb+Ekq,R +[CKIQhmVFF$J`VdQ5m'YemhE`p&D5E6j5(VmrPl$l$k3rXTPHl)!0@&jc$0ZABIP +&mp&"JI!#DaENdTMml81I`#icTcGeq@6Upd1K&[m+1ImYiqD1UMI2ikh!"rA+h@G +FD3IbmLC@e9$jp9!k6JUl9a2!KP1+'B$U)Bc230$[U!%J"*%91SCK"!!!#4G"4%0 +5!`!1K3jG#k55),ibhjhrq-jHjTcIRm3X@FlH1F3CSh%X80,b[R0QLXqEJ*aX40@ +UcFUD#%5Rl(e8JllrGqBa*dc)b``NKG+`*T5N3Y"1UPMIThV[djD9N!!b0-6@8G9 +SS`-*3XV@EJZ,ppfG(@H&VG9l$HjZ05)#-d3M(d0&9JkT3rK[IJhrMG+c8Cl(B+4 +U`aFMihF0N!#jkkJ(bAf)aK,IG)P[ca,IG-NC,*)9Q2S@4ApLIjK&+S'Id[Z4`Ve +!C&F!hk33*VJjb0NB50)-%!X)p`RCp6"I8pTh4UI[+E8`dFID4ZkGr@lA$AQDlbl +dTY[ZH@,hr!+XiD9#H+cpQa8c%jVX(JTT(9@GRD&+8bJflXSeSl4`5cY$Y5BUZhI +&Y(p9rDdY9MhmA'a'S26RfD0D,'"MJPS)PR+'Y$*IGS48-42MQe(`[2"&@fPYS(Z +$2,ZVS#hMPle6m-bhq8GG`NZPN!#-)R+V%99PNPlh36fhm*9bM*6G5(DLl6*[`RR +N%1kYe0apJX"08h9pBpI"UeK@ZP%fDY[@f0f"j$FB'16,"hmb'lqqr(ZIF,[qqr( +8P4F2LPmU8Y6lJ@,C*HD'0C[m5%SlDaYhr0f9c5@RlpTrl%Ba0h%pIrF&([T)hhC +PQkhZV!V"p*ICGPYr00)ATYFTIIZZ+Zh!F'lqJ+RpekRmf5JSrZ#`Jfc9YhilkVT +Hh9L([6AGJPqp[j5#5'i#SGl@1!l!f0$([Z4B9)JpbTMr8K%pV8&b'lM9+a*8%hV +&U'bb498Z@DQS*MRZ&3U9S9IX$jNN*bhPP@UI8TA)Arc+eR608jlGh#RG(Y%mCIV +ZHjY0eR5&1qC1dXc90emD5,Ej@(eFD1XV&rqi5CRVdF[i&43@eN[fU#"2lpYa(Vl +C*ZUUNZp0XGNQB,2*LUI#aR9CrBK0&Sp+b8*6C1J9E63Tqf"5i25flDL4ELQ!0#R +1JdB29[1P&-F&`EQV34i-5ZXME9iP2CJ#Sq!JM5UKU,HpAUd@9Ujj,Ce3erhrA2Y +0)fVEk2m'%q('2UUC%2HLD&+GqD$mXUXk4$HdCk9'VaEk0mH(e6-fT4pmjRa9e3I +09fm)JH',*UjGkCYBVDUp[eRI+)5dJF4`UL@ija2YP0UeSHUUm2!Y1A!N#i,p%r+ +mraE)KC!!h)mYI"M-*-j`h-92m%VhR)6A)GbRK)!S@)q!m3bE39ZF$L3#NlajP54 +K$2XaQX4Q&+bEm4`j1AMiN!!D$8bLH48j)NdQ4ZlPFU1$$j(RK3$Nj8cC#bja3Z9 +@VceajE1aV$3CP1@85*e1e)0Q)jGcLA5!ibD49*QcdSUIb6+I1lrc$,UNCTa,i@! +(l&NX8Z!UXl)Fc@N10p%$TmlaarF12*%VqVQP+IbmGAC[Z(*i4%i0ElMdXHT++-Z +e+['9N53H0#4Y9,3%Ib#8d),3L422HK!GJGBFY,LYf`X9ljIeI"9HZ54kVCUF308 +$Ta+%AGi0FXiSpX5"dhYD$!,RQ3U0(P3fjfdBeq`NrZ'e,erl6RVGI#'mVR"`phb +T-2,3[')Sd$cb3&!%V`adK&KR!aa2Y%%,Z,h0rNH#iVJi,$5IJe1*KV4GN8AeTLJ +dhii*%jh+4+0lL-6G`Gb!1(p'+l"R#Xqlj[FAI)G0QQ+9i'Uh+QCdXfkhYb)Q$Qa +("&X5L'&#mPXXm@0)Ucd%5eJUcINKX48pJ6d%PbS*K&[ci9Z%V*(I3hJPL`MrdJS +I3`D53``T(FqmS[EkqkCA(rKLZIm!-KZ&[1GiTPFGl*ZpSH[blmr0kANl3fU1pr3 +DII1R0R@Yr)*!qJH3!1C*GFpaYEH[Hmh&ZN*A@%M6!%pD0diEiCSl$VHQjr`k#`[ +26@IZ9DI$daI6KAA2pN(q9D3Y+FMIp2MMdq%6Qblq,Td1hibPQPH&k,ZJH0C&,1f +SJfdU0CD1i33[B404!YdjT'r8eK+UMqe@Db3GV(dcl%e2@"m1dpJq%Rh(6	H,q +%2[!CM`9RLm[r%li9iPZYi6IUKXJLDiC`*LQ1j@0C1-R3A,AUd"L@,!$X'#)f9Q` +GU4qjDTIlE$mqA(M5%LlfVkUm(4R$6f-Y*2qH0D6p36il*,[YSYC3Ef@,rIqX9*l +!Ebf0'B$Bc0CR("AbR`1@-IY,[Y#8e9pF9R&@mL*83(aXZ8)Z"La%VB`"J[[p584 +Bj!eI5+Z3!-m'FQle8ke"Xl)9E+f!P-EiV3U5Rr1e(UmFZASXGdNpP#jSPS%Mep0 +4%N&XLQ8J!kB+PhfShJLZT1Hk&EZ'!AIMZcc23,,Q+,p414k9S,B6,'a82bfhAaY +ETNrA%56ja02ZG*T5j4lME!"-qUA2Y$MMJiY4T!6X@Z)k)D`B8d'!AjJ-cVeXTE3 +(16-(E(Pb4Q#Tj'@9#%5hl5$Glld`DH2L9KVmeH*EFa9hpIDl1`$QYF(BlY&bTX2 +dqBA$reLmd2TDRl(Y0,(GBIAPhAX"rrC*9qj6Dcd$H#M1@jc&$Y'NGA48bXlAXh* +S@kH!Zkfbli`-Z2M"q(b&pHX4I'G4%de6YE9A(RA@qh)a&f[!UpMFMmNq0q"eB[p +iVX)kEUcG1DPY3V0$`62A6#EC@@pc!%i&h!kia8UMBYEe3)A945F$!Zi8m+KTci2 +1HV%pT#rHl4bkG*dEl!9mF@EmL(@Qr)X@N!!HQJGM-lYP`%2C`r@*QVC6V('0%[" +TN!#F,[k0%(eX#hU3!0aHNqRj@JSe6JF*I04KqTHADUE2Tc,B-J[*j9f1hMkq&-B +!U*!!KfX`AB-8B"Ah@IS[4f&li'IQ$1CAe9if%aiqGN!&Q%S4&CiQhDIf@FS1cNU +jaA2YaBpQiQbY3i'CP4mCLmX+L8rILUK3`8dZHmjaV0YkRM[jP2ccRlVip*4Me-q +APMjP-@A,$9P,I85&#Ulqe@iaGfcqU-aPXh*qlF1-@ZZSlH5C1mA0TJqUQTkf2j& +8)5`qd`4i3-FVSVN)cQ3YT[jL52V`9&+&J$mj!EMGASjQX`p@2&,*6%A`c5D4[Tl +00DT3H*AkL5leI''P)l9im[YlhCrhTp2DG*J[E4#Y4L*[6e`je+K#q,'4%64@!PY +K&Z!k%mV9AKd1kGd&SBA5Q+kqLIFl@5H@2&IJeq44!*!$'!!d!!!"(!&S!!%"!!% +!N!8$k!#3!j3!N!-)!#!J!3!#!*!&('&eFh3!N!-"4P*&4J#3"B"*3diM!*!&J!# +3!`G"8&"-!*!&!3!!!3#3!`+!!!!%3!!!#5!!!"13!!!!*mJ!!%%%!!#"!J!"!!% +!!JI!J!32i%!)''!J%"[m%#3DP!K-'[3NRc)%-N`ek2NN05Jb%$Ii*!J`i!J%(q! +3!J$!)!%$m%!!J!#!!%#"!!!JJJ!!%q3!!!R)!!!%N!!!!!)J!!!"3!#3!i!!!!% +!N!-$J!!!"m!!!!rJ!!!Im!!!2rJ!!(rm!!$rrJ!"rrm!!rrrJ!Irrm!2rrrJ(rr +rm$rrrrKrrrrmrj!$rRrrN!-rrrrq(rrrr!rrrrJ(rrr`!rrri!(rrm!!rrq!!(r +r!!!rrJ!!(r`!!!ri!!!(m!!!!q!!!!(!!*!$J!#3"#!IU5!a16N`,6Nf)%&XB@4 +ND@iJ8hPcG'9YFb`J5@jM,J!!&8J!N!-"GJ"1F8U$CKT"l3!J-,`!#$&m2c`!!M& +m!!%!"$&mUI!!"Lm$,c`!!"5Q3IVrd0$m!+)[#%+RB3!#*Ylm!""R%NU$C`4`!8j +e6Ud!)Q%!!pDTp%ja5S0Q!URdF!"1G@"b38a"4%4$69!!!`#30&"b3@e)jf$`G$+ +I`Lp)!#!J6b*8-@N!&!!B)8!!*$&m!!%!,0+4)8%!,U!#hm*-h`m'6R9+1!THC`` +J+J!)C``J3#!3C`B[1[q%6R9)jam'3IVrRR!-)LS!"-+i!aTKT'B!!4*)H[q16VS +%i&K2X(Vr@QB!!1bK'Li)##S!"J!%C`BJH!+QS"XX+J!%+LS!#"JU!!5Ae*A8)$V +r9U%HCJ!!c#a))$Vr5L)'`VJ$'PK"B3$r8L!krd,!Z!-D3IVr2L#!5S9Q"+%LB!3 +J4D!RCJ!!Q#T),cVr!Lmkr[S[1[lb,cVr!Lmkr[T)H[m5,a!J1[m!8B""q[lf))! +J$P#!3IVqk##!5(S!HQ%!#L6Hr!!J5N"R!URr)%kJ(b"(S"Yb!")%j`RM'H34!!% +!)!)"!1!J6D"T!J!!(i!")%fJDYA8ep4"q[kB5T!!C`K`!D'BF!1KQ#"0*8J!#(! +!60pJq%je60pJq'!!rZ!J6U!IeG6Ae#"(S"X`1!)J-F!+B*()B1"19J!!51F!1#K +Z!!a(q[jD4IVq@L!8X**Y"#!5+)"+J'm5)&-LEJ!)SLiJ&0'6NC*`!'!%-$crf8c +I(!"1ANje6PEreNMR%aJX,J!35IVpmN)(S4SY52rQ5Li!#fF')(J#TU!E,c`!!+$ +m6VS#RLe!rqTB6fF!!Ai[,[rU6VS"q%S!@%pR"R!"B!!"FNKZrrK)E[rd5'lrlNk +k!j)J,[rdS4iY52r`6qm!$'F!!8JJ,[riS4iY52rmC`!"1LmZrrJ[#%kk!qC+VJ! +88%pQ!!#8@Bm[2%024%9`!$m!U"mQAb!,ChiJ%h)Bd)%[!%kk!Y`-3!!$@%pQDL! +0FLM3J5e!rpSJ%h3Bd))Y32rH,`"1ZJ,`5-!Y32rL)%ZJ+5!Zrpj3J#P!!#KCMbm +,6VS3d#!IFL#3!)%T3!!X,blrr#mZrr3[,[r`,`B[,J!-5'lriLmZrpT)H[kX6VS +)9Lm,UD02l`!N+@lrkJ!-+@lrm!!3+@lrp!!8+@lrr!!B,c`!!+'B6VS"L#e!rpB +[2!!!U*p1ZJ&k)LlreV#"9X0%!dL$5--T3`!F+8B!)#PZ!!`!*%Kkr*!!2cbJr#m +m!!#Jr%kk!54BMam!6VS3@#mm!!#KQ%kk!6T+J%r[!!aR"%kk%&B`1!&Di%!-3!! +'CJK"qJ!D)FJ$2(i")'lrjU!E%!G-lKM)rm*1ANje6PB!!%MR!4K#"bmm!!#Jr%k +k!2)S3#!-@%pR5#m-6VS!8%S!@%pR2#C-,bX!$$mmS2`[2!!!S2a1ZJ#L@)mI!%k +k$pBJD`!3S"mJD`!BS"m`1!&Di%!-3!!'CJC`!#(!!caq!4!(61iBJ2rd6Pj1G8j +@!!")ja!)+'i!#(B!$+a"6%&%!!*Q&!bX4%008!!'CJT`!l"X!!TQ!RB"%!0-lK! +)rrK1ANje6PB!!&Q22cbSER!"(`"1ZJp-@Bmr2+TZF!%I!%kk$ciJ(l#ICJB`2!) +!B!3`2!3!6Pj1G8j@!!![!c!m#!$!EJ!+FJ!b!%U"8X0%!fF%F!&J!R!!*Llrr%j +H6R919J!!51FI!$iZ!!T)abm(6VVraKS!F!!3"3a!!!&B6fB3!NF(rdkkrhb`4fi +%F!"J+PQ22cbSRh!"(`"1ZJl#+"pCMcm((`91ZJkf,"qiKPI$4!0R"(!!B!)J"Nc +Z!2Mrl%jH6R919J!!51F4#$iZ!!iJEJ!)+&"f!(!!-"3-J!!!384Q,R!!-#`!!Jb +!!!"$8QBJ$%IrrfFB)#`!"%*!5%$J5#)m!*!$rm+!5-HqJ@B#GJ%3!dcZ%)Mrp%j +H6R919J!!F2m[!%KZ!!K1Z[qB5J"36fFD)'i!##!S!!4#3%K!i%JL2!#3!rr#J$! +"B!*`rdjH6R919J!!F2m[!%KZ!!K1Z[pL5J"36fF3)'i!##!m!2q3!m#S!!4J!R$ +r6Pj1G8j@!!")j`!B*Qi!%#KZ!!`JEJ!)-,`$!A!!+)!'P!!!!53'P!!!!NJ'P!# +3!b!'P!#3!i!'P!#3!i!'P!!!"*!!"T3!!!%N"T3!!!53!!D8!*!$I!D8!!#!!#D +!"T-!N!-N"T-!N!-J"T-!N!0)"T-!N!-qF!"-lKJ!rrK1ANje6PErp%MR%aJQEJ! +),8[rp!DZ!*!$*2rd+'lrp!DZ!*!$)2rd,@lrp2ri"Ui!N!0)rr3YE[rdrr`'VJ# +3!clrp#!Zrr53!+i!#,#Z!!aM"R"PB!!!X%*(3NCJ4R!!-!F-3!!%9F0%!fF%F!" +J$(!!-!GCJ()%6VS0)R)!-JFAJ"J!F!!`"b"ZrrM3J$''#!"`!$!(%$-)!()"iDR +F36!(8NG`!$!($%!!*'@`3NGm!@"'F!!`"`a!!!&9`d3$C`4`!'!-F!!`"e1!FJ* +1ZJc-FJ!b"aQ!'!"`!$!()'lrr0#!-BB)!(!!-!F30!J!FJ(KUGa"-!G54h!!-!F +-3!!ICE"`!%cZ'-Mri%jH6R919[rm51F2'#CZ!!`SEJ!83NCJ$R!!-!E3J%*d#!! +`"P*'F!!`"R)!-Li!%Y+"XS"ZiN*'H!*J!!#D3N9#4h!!-!BJEJ!)jB!YF!J!rra +JE(!"`+lrr0j!F!!`"A)!-JCd!"3c'!"63NM#Y)"[+(!!-!I3J()!-M3)!%U"CJa +`!$!(d)!jK!J!9%4`!$!(d)!q0!J!B"K`!$!'FJ!b,J!5dS(5J(!!-!I3J$Q"#!! +`"9*&)#lrr1+),8$rr(!!-!9b!$)'G!!8-aJ!Y%"LJM!'8NDmEJ!5C3$rBNcZ'2$ +rj%jH6R919J!!51F2'$iZ!!iQEJ!3+'i!#$JZ!"Bk"qC0F!!`"h`(c%"q!(!!-!8 +30!J!l#Kb!")!F!(!!A)!%J$HJ5!(d)"b!$)c#!!Z!9*'F!!`"R))XS"Q"N*'-!9 +54A!!-!63J,#(BX"`!$!%d)!L"j+!%!&-lKM`rqK1ANje6PB!!%MR$`Ji,J!52Li +!$LKZ!!Jm"qC1F!!`"hS(bN"`!$!'IJ!H0!J!F!!`"A)!-J65J1D*CbT6J@F@8i& +Q)R!!-!C8J()!%M3)!%K"3N'1JA!!-!C5J()!%M3)!1'*MS&`!$!&i+p`)*!!"(, +ri+R#Kc!"61i3m2rX6Pj1G8j@rqT)j`mB*Qi!##KZ!"!'VJ!!!53!&!DZ!!!#5!! +8,@i!&2rd"Ui!N!-J!"3YEJ!8rrJ'VJ#3!i!!&#eZ!"6rr"!6jJKb!")!F!I!!A) +!%J"536e"rqS3%q))FJ!5!(!$`!&b!")!1!&84(!"kDJp32rbF!!3%h*!`J"`!"! +"28$rm(!"kDK6J$e!rqj`!"!6FJ(#!'F+F!!`,[rZ8i"J!R$r28$rl(S)5Qlrm'G +@,bi!&#mZrr4`!$!Zrr)[!#!,8S![!%kkrcKb!$)!ji(D35mZ!"3[,[riF!!`,[r +b,`![,[rd6VS)I#mZrra`!$!Zrr)[!#mZrr3[,[ri6VVp)Nr[!$"#4f!!!2C+E[r +`Cc*`!$!Zrr)[!#mZrra`!$!&,`![#dkkrGjm!"`!F!!`"L"Zrr4b!")`#!$D38r +[!""J'R!!-!3[!(!!-!8[!#m,6VVq,M`!fN42l`!-['lrl'B3-!G54h)!-J"#0"J +!B!!!MVaZrqjQG%TZrr"R-R!!-#lrmLm!,blrr(!!-!8[!#m,6VVpF(`!(!"`!$! +')'lrp()!%M!)!0T"6qm!%'!DF!!`"#m!F!!`"5m!,`Y1Z[h!2!$D4%r[!!a@4Q! +8F!!`"e1!FJ!b"aQd#!!B!$!(8NF`"P0'5N"Qj'!5%!E3,[rV-JG54h3!0!%CJ#J +![Qi!$Q8!r`C`!$!&AS$QL%cZ'2$rdNjH6R919[q'51F2'#eZ!#6rj!DZ!*!$*!! +N,@i!*2rd"Ui!N!-J!#3YEJ!NrqJ'VJ#3!dJ!*#eZ!#6rq#CZ!"c@r!%Ne[`#50E +m!#$@r!#!e[`!J#e,rpM@r!53!#e,rpc@r!%N,8[ri0Em"*!!,8[rm0Em!(`Y5rr +)er`!!)!!)!Z3!+i!(,#Z!#"M"R"PB!!&8RS!3NFJ,[r)d,`!!)!!,8$rc#KZrmJ +YI!!!J!$rr%KZrr`[,[r))'i!#%k3!%UZrra36fB'F'GJ!!8B)!a5J,#ZrmaMC#e +-rlSYE[r-rliJ$&+!N!#ZrliY32qf)#lrZT!!V[r),8$rXL!Zrlk3!+lrZLe!rkj +R$#"-)Qlrb#!ZrkkL,LKZrklCl[r)5'lrXLmZrmJJEJ!)6T!!)#lrXV#ZrlC36f3 +'F'GJ!!5U(9crah!!%#lradM!d)"63$e!rqa`!$!Zrqc3J$e!rqiJEJ!3)"$3VJ! +-,8$re#mZ!"`[,[rF5(J"*#m-6VVmG()!-J"+JGR",bi!(#mZrpK)H!%N,blrh%k +k"E`[,[rJ5(J"*#mZrp`[,[rB6VVkCLmZ!"`[,[rFF!!`,[rX,`![$%kkr#jb!$) +!5S(C`5mZ!"`[,[rBF!!`,[rX,`![,[rF6VS&FLmZrr"`!$!Zrq`[!#mZrp`[,[r +B6VVk'(S!3NFYEJ!-rp"2l`"JB!!$X%*'B!!!Q(!!-!G+J'Cd)!a5J,#ZrmaMC#e +-rkBYE[r-rkSJ$&+!N!#ZrkSY32qL)#lrTT!!V[r),8$rRL!ZrkU3!+lrTLe!rjT +R$#"-)Qlrb#!ZrjUL,LKZrjVCl[r)5'lrRLmZrmJJEJ!)6T!!)#lrRV#Zrk*36f3 +'F'GJ!!0HHJ!D((i)F!(!KGa!F!!`"L"Zrq$3J$``#!$LM6!(8dG`!$!'$%!#5'8 +!rf!%4J*)F!!`"Ja!!3"N%#!Zrp"5V[r3)%!3KQ!!!ZS%4J%!F!!`"L"ZrqM3J$J +`#!"`!$!')'lrj()!%M!)!$e"rm*`!$!Zrm*+J'-!!+*JH#!-8S#`V[r-Bf3Y62q +Q,@lrc2qU)!a5J*!!V[qU,8$rSL!ZrkD3!+lrb#e!rjiJ,[qUN!#ZrkBY32qDC`` +J6#*ZrmJJ,[qDSLiSE[qDfHlrb%KZrji[,[r))'i!#%k3!#!Zrjk`V[qL8%pN"R" +RB!!#G(!!%"c[U)U!8%G`!$!($%!!''-!rhj`)*!!,[r$F[rJUF+&f%&`!$!Zrm, +JVCjZrm*#4Q!!!*K`!$!(5S"QG#!-8S#`V[r-Bf3Y62q5,@lrc2q@)!a5J*!!V[q +@,8$rML!Zrj+3!+lrb#e!riSJ,[q@N!#Zrj)Y32q'C``J6#*ZrmJJ,[q'SLiSE[q +'fHlrb%KZriS[,[r))'i!#%k3!#!ZriU`V[q18%pN"R"RB!!"b(S!'Kaq#(!"`)A +F3(!!-!BJE[r`d)!m-!J!iSd`"e0(['lrlQ8!rf5FE[rZF!!`"L"ZrrM3J$e`#!$ +ra(!!-!BJE[rdFJ!5-!J!28(r`R!!-#lr`NU!B`!!T'"i)!a5J,#ZrmaMC#e-rkB +YE[r-rkSJ$&+!N!#ZrkSY32qL)#lrTT!!V[r),8$rRL!ZrkU3!+lrTLe!rjTR$#" +-)Qlrb#!ZrjUL,LKZrjVCl[r)5'lrRLmZrmJJEJ!)6T!!)#lrRV#Zrk*36f3'F'G +J!!$qF!!3(1qSLS"34h!!-!F-3!!BB`$rIR!JN!!Zrm0brq#T`SA6E[r%F!!`,[r +#i+fHE[r#F!!`,[r%5S!QE[r3Pm#hlJ!-C6BJE[r38Ulrd"#E)'lrd&+Zrp!3Qb! +Zrp"5V[r3)%!3Qf!+)'lrd&+Zrp!3Qc!%8d4+3'EZB&C@4#!Z!"M3VJ!8FJ!b,[r +%*#lrd*5Z!!b5JLC!Pm&J$L"Zrp"5V[r3%*X`"&0%5N4R$#!Z!"M3VJ!8X)YLiLC +Z!!aJ#L"Zrp"5V[r3%*X`"&0%5N"QlL!Zrp#`V[r8C3$m5#!Zrp#`V[r8C`4`Cf! +3)#lrd*!!VJ!-)'i!%##!F!"-lKM`rfj1ANje6PB!!%MR$aJQEJ!81#i!#LKZ!"" +J!!%`2!3q,J!18NDmEJ!1C"*`!$!'FJ!b""!d#!#`0"J!CHC64lK(C"*`!$!(FJ! +b""!d#!#`0"J!BZLq4Q0)F!!`"RS!'M3)!(!!-!Gb!$)''E3)!"J!F!!`"aQ&#!" +`!$!'d)!k-`J!F!!`"p#!FJ!b"Y+"0l-)!"J!F!!`"p#!0i8)!'##Z%GQ"P*%B!! +!SR!!-!4k!"Sd#!"`!$!(FJ!b""Qd#!!B!(!!-!FCK3J!F!!`"0#!1M-)!(!!-!I +3J()!-J65J6Hc#!!B!(!!-!I3J$H&#!"`!$!(FJ!b"*!!JA)!-Li!$R3!0!G5JT+ +#XS"M(Lm,,`a`!$!(,`"`!$!%,`"1Z[lQ1!G54%r[!""J)#m,,`a`!$!Z!!i[!(! +!-!G5J#m!6VVqa$e(!!j2l`!3F!!`,J!1FJ!b"*!!JA)"XS"Y!2l!61iBm2rS6Pj +1G8j@rra)j`mB+'i!&!DZ!!!"*!!8*Qi!&%*'B#"`!$!')'i!#()!-JBCX!J!'!" +`!$!'d)!hKJJ!-!C54VaZ!!jPfLm,,`a`!$!Z!!i[!(!!,`"1Z[j)3NC2l`!3B!3 +`"P*'['i!$Q31F!!`"R)!%M3)!%U"CqK`!#e!rraJGR!!-!C+J'-NF!!`"R)!%M3 +)!(!!-!C6J(3!&$3)!**#5-%J,[rmikJY32rmF!!`"RJ!'$3)!#SZrraq!'!1)!I +ML()"`S@#J#i"iSd`"&0%5N"QkR!!-!E3J()!-M-)!#"Z!"$PJ5'('!!`"P*')#l +rr&+ZrrbmEJ!1CB4-lKM`rq41ANje)PmJAk!P,S"U!N+A6Y%LAa)I-"p+!@F%TdC +J!U0',SK1d5*I%Km`(b"I5J&R"+C(B!+L4dl4)Pp`!D'B6Y%L,`!%)#m!#%(k!!S +bI!!#6[#5rQ!'6%%)!8je6VS!*#!"6R8L,`!%)#m!#%(k!!SbI!!#6[#5rQ!)6%% +)!F0!6R9+J'X85S&V"Nkk!%C1G85"6VS!2N5"6R9%J%U"D`T1ZJ!`4)"%J8je4)& +1ZJ!N4)"1G5)[!!3J,`!)3IS!#M*m!!*1m*,qB!K-33!"`d"1G6m"5%&+3@BF)J" +#38K"C`U#edK"5%!`!8K!J0mb!%*!5%"1G8K"2S)[!c3!*J&b!8*!5%"Q$%K!-!* +b!'!@dN&P%Y4#dB#`JfAdN!#$dN%)`3!!C1iQ(c3I6R8!!!%!N!0AHJ!!9RS!!!* +k!3,f[$J`!*!$(!*U!"&%394"!*!$NPT&8Nm!N!1H4&*&6!#3!kT$6d4&!!-!YP0 +*@N8!N!2Q4%P86!!(!2*"6&*8!!B"8P088L-!!!'QGQ9bF`!"!E*69&)J!!%"bP" +cCA3!!!(L8%P$9!!"!Hj%6%p(!!!#"NCPBA3!!!)53Nj%6!!!!Kj'8N9'!!!#+NP +$6L-!!!)fBA9cG!!!!N)!!2rr+!#3#Irr#!!#M!#3"[rr+!!#[`#3"3,rrcJ!!X- +"![DF!!(rra`!+SB"![D!!!$rrbJ!,-`"![A-!!2rr`!!35i"![A3rj!%!!!Y,!# +3"!3"rrmJ!#dk!*!&KIrr*!!YZJ%#pR3!Krrr!!!Z%!#3"BErrb3!,Q!"![C-!)$ +rr`!!,Z)!N!@errmJ!#p!!*!&J[rr!!![XJ#3"!2SrrmJ!$!3!*!%"!(rrb!!-$` +!N!@#rrmJ!$"-!*!&KIrr*!!`A!%#pP`!Krrr)!!`E!#3"BErrb3!-(`"![CB!)$ +rrb!!-)`!N!@"rrmJ!$#F!*!&J2rr!!!`V!#3"3(rrb!!-,m!N!8#rrmJ!$$d!*! +%!J#3!b!!-4)!N!3#!3!()!!a)J#3"B$rr`!!-5i!N!9rrrm!!$9P!*!%!qMrr`! +!0TJ!N!3$k2rr)!!rX`#3"[rr)!!rc`#3"B$rr`!!2pX!N!@!rrm!!$rl!*!&J2r +r)!"!"J#3"[rr!!""#J#3"!C`FQpYF(3)a#"cG@CQDAM(93: diff --git a/tcl/mac/tclMacResource.c b/tcl/mac/tclMacResource.c new file mode 100644 index 00000000000..1695ca6ca1d --- /dev/null +++ b/tcl/mac/tclMacResource.c @@ -0,0 +1,2205 @@ +/* + * tclMacResource.c -- + * + * This file contains several commands that manipulate or use + * Macintosh resources. Included are extensions to the "source" + * command, the mac specific "beep" and "resource" commands, and + * administration for open resource file references. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <Errors.h> +#include <FSpCompat.h> +#include <Processes.h> +#include <Resources.h> +#include <Sound.h> +#include <Strings.h> +#include <Traps.h> +#include <LowMem.h> + +#include "FullPath.h" +#include "tcl.h" +#include "tclInt.h" +#include "tclMac.h" +#include "tclMacInt.h" +#include "tclMacPort.h" + +/* + * This flag tells the RegisterResource function to insert the + * resource into the tail of the resource fork list. Needed only + * Resource_Init. + */ + +#define TCL_RESOURCE_INSERT_TAIL 1 +/* + * 2 is taken by TCL_RESOURCE_DONT_CLOSE + * which is the only public flag to TclMacRegisterResourceFork. + */ + +#define TCL_RESOURCE_CHECK_IF_OPEN 4 + +/* + * Pass this in the mode parameter of SetSoundVolume to determine + * which volume to set. + */ + +enum WhichVolume { + SYS_BEEP_VOLUME, /* This sets the volume for SysBeep calls */ + DEFAULT_SND_VOLUME, /* This one for SndPlay calls */ + RESET_VOLUME /* And this undoes the last call to SetSoundVolume */ +}; + +/* + * Hash table to track open resource files. + */ + +typedef struct OpenResourceFork { + short fileRef; + int flags; +} OpenResourceFork; + + + +static Tcl_HashTable nameTable; /* Id to process number mapping. */ +static Tcl_HashTable resourceTable; /* Process number to id mapping. */ +static Tcl_Obj *resourceForkList; /* Ordered list of resource forks */ +static int appResourceIndex; /* This is the index of the application* + * in the list of resource forks */ +static int newId = 0; /* Id source. */ +static int initialized = 0; /* 0 means static structures haven't + * been initialized yet. */ +static int osTypeInit = 0; /* 0 means Tcl object of osType hasn't + * been initialized yet. */ +/* + * Prototypes for procedures defined later in this file: + */ + +static void DupOSTypeInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr, + Tcl_Obj *copyPtr)); +static void ResourceInit _ANSI_ARGS_((void)); +static void BuildResourceForkList _ANSI_ARGS_((void)); +static int SetOSTypeFromAny _ANSI_ARGS_((Tcl_Interp *interp, + Tcl_Obj *objPtr)); +static void UpdateStringOfOSType _ANSI_ARGS_((Tcl_Obj *objPtr)); +static OpenResourceFork* GetRsrcRefFromObj _ANSI_ARGS_((Tcl_Obj *objPtr, + int okayOnReadOnly, const char *operation, + Tcl_Obj *resultPtr)); + +static void SetSoundVolume(int volume, enum WhichVolume mode); + +/* + * The structures below defines the Tcl object type defined in this file by + * means of procedures that can be invoked by generic object code. + */ + +static Tcl_ObjType osType = { + "ostype", /* name */ + (Tcl_FreeInternalRepProc *) NULL, /* freeIntRepProc */ + DupOSTypeInternalRep, /* dupIntRepProc */ + UpdateStringOfOSType, /* updateStringProc */ + SetOSTypeFromAny /* setFromAnyProc */ +}; + +/* + *---------------------------------------------------------------------- + * + * Tcl_ResourceObjCmd -- + * + * This procedure is invoked to process the "resource" Tcl command. + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ResourceObjCmd( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *CONST objv[]) /* Argument values. */ +{ + Tcl_Obj *resultPtr, *objPtr; + int index, result; + long fileRef, rsrcId; + FSSpec fileSpec; + Tcl_DString buffer; + char *nativeName; + char *stringPtr; + char errbuf[16]; + OpenResourceFork *resourceRef; + Handle resource = NULL; + OSErr err; + int count, i, limitSearch = false, length; + short id, saveRef, resInfo; + Str255 theName; + OSType rezType; + int gotInt, releaseIt = 0, force; + char *resourceId = NULL; + long size; + char macPermision; + int mode; + + static char *switches[] = {"close", "delete" ,"files", "list", + "open", "read", "types", "write", (char *) NULL + }; + + enum { + RESOURCE_CLOSE, RESOURCE_DELETE, RESOURCE_FILES, RESOURCE_LIST, + RESOURCE_OPEN, RESOURCE_READ, RESOURCE_TYPES, RESOURCE_WRITE + }; + + static char *writeSwitches[] = { + "-id", "-name", "-file", "-force", (char *) NULL + }; + + enum { + RESOURCE_WRITE_ID, RESOURCE_WRITE_NAME, + RESOURCE_WRITE_FILE, RESOURCE_FORCE + }; + + static char *deleteSwitches[] = {"-id", "-name", "-file", (char *) NULL}; + + enum {RESOURCE_DELETE_ID, RESOURCE_DELETE_NAME, RESOURCE_DELETE_FILE}; + + resultPtr = Tcl_GetObjResult(interp); + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[1], switches, "option", 0, &index) + != TCL_OK) { + return TCL_ERROR; + } + if (!initialized) { + ResourceInit(); + } + result = TCL_OK; + + switch (index) { + case RESOURCE_CLOSE: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "resourceRef"); + return TCL_ERROR; + } + stringPtr = Tcl_GetStringFromObj(objv[2], &length); + fileRef = TclMacUnRegisterResourceFork(stringPtr, resultPtr); + + if (fileRef >= 0) { + CloseResFile((short) fileRef); + return TCL_OK; + } else { + return TCL_ERROR; + } + case RESOURCE_DELETE: + if (!((objc >= 3) && (objc <= 9) && ((objc % 2) == 1))) { + Tcl_WrongNumArgs(interp, 2, objv, + "?-id resourceId? ?-name resourceName? ?-file \ +resourceRef? resourceType"); + return TCL_ERROR; + } + + i = 2; + fileRef = -1; + gotInt = false; + resourceId = NULL; + limitSearch = false; + + while (i < (objc - 2)) { + if (Tcl_GetIndexFromObj(interp, objv[i], deleteSwitches, + "option", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + + switch (index) { + case RESOURCE_DELETE_ID: + if (Tcl_GetLongFromObj(interp, objv[i+1], &rsrcId) + != TCL_OK) { + return TCL_ERROR; + } + gotInt = true; + break; + case RESOURCE_DELETE_NAME: + resourceId = Tcl_GetStringFromObj(objv[i+1], &length); + if (length > 255) { + Tcl_AppendStringsToObj(resultPtr,"-name argument ", + "too long, must be < 255 characters", + (char *) NULL); + return TCL_ERROR; + } + strcpy((char *) theName, resourceId); + resourceId = (char *) theName; + c2pstr(resourceId); + break; + case RESOURCE_DELETE_FILE: + resourceRef = GetRsrcRefFromObj(objv[i+1], 0, + "delete from", resultPtr); + if (resourceRef == NULL) { + return TCL_ERROR; + } + limitSearch = true; + break; + } + i += 2; + } + + if ((resourceId == NULL) && !gotInt) { + Tcl_AppendStringsToObj(resultPtr,"you must specify either ", + "\"-id\" or \"-name\" or both ", + "to \"resource delete\"", + (char *) NULL); + return TCL_ERROR; + } + + if (Tcl_GetOSTypeFromObj(interp, objv[i], &rezType) != TCL_OK) { + return TCL_ERROR; + } + + if (limitSearch) { + saveRef = CurResFile(); + UseResFile((short) resourceRef->fileRef); + } + + SetResLoad(false); + + if (gotInt == true) { + if (limitSearch) { + resource = Get1Resource(rezType, rsrcId); + } else { + resource = GetResource(rezType, rsrcId); + } + err = ResError(); + + if (err == resNotFound || resource == NULL) { + Tcl_AppendStringsToObj(resultPtr, "resource not found", + (char *) NULL); + result = TCL_ERROR; + goto deleteDone; + } else if (err != noErr) { + char buffer[16]; + + sprintf(buffer, "%12d", err); + Tcl_AppendStringsToObj(resultPtr, "resource error #", + buffer, "occured while trying to find resource", + (char *) NULL); + result = TCL_ERROR; + goto deleteDone; + } + } + + if (resourceId != NULL) { + Handle tmpResource; + if (limitSearch) { + tmpResource = Get1NamedResource(rezType, + (StringPtr) resourceId); + } else { + tmpResource = GetNamedResource(rezType, + (StringPtr) resourceId); + } + err = ResError(); + + if (err == resNotFound || tmpResource == NULL) { + Tcl_AppendStringsToObj(resultPtr, "resource not found", + (char *) NULL); + result = TCL_ERROR; + goto deleteDone; + } else if (err != noErr) { + char buffer[16]; + + sprintf(buffer, "%12d", err); + Tcl_AppendStringsToObj(resultPtr, "resource error #", + buffer, "occured while trying to find resource", + (char *) NULL); + result = TCL_ERROR; + goto deleteDone; + } + + if (gotInt) { + if (resource != tmpResource) { + Tcl_AppendStringsToObj(resultPtr, + "\"-id\" and \"-name\" ", + "values do not point to the same resource", + (char *) NULL); + result = TCL_ERROR; + goto deleteDone; + } + } else { + resource = tmpResource; + } + } + + resInfo = GetResAttrs(resource); + + if ((resInfo & resProtected) == resProtected) { + Tcl_AppendStringsToObj(resultPtr, "resource ", + "cannot be deleted: it is protected.", + (char *) NULL); + result = TCL_ERROR; + goto deleteDone; + } else if ((resInfo & resSysHeap) == resSysHeap) { + Tcl_AppendStringsToObj(resultPtr, "resource", + "cannot be deleted: it is in the system heap.", + (char *) NULL); + result = TCL_ERROR; + goto deleteDone; + } + + /* + * Find the resource file, if it was not specified, + * so we can flush the changes now. Perhaps this is + * a little paranoid, but better safe than sorry. + */ + + RemoveResource(resource); + + if (!limitSearch) { + UpdateResFile(HomeResFile(resource)); + } else { + UpdateResFile(resourceRef->fileRef); + } + + + deleteDone: + + SetResLoad(true); + if (limitSearch) { + UseResFile(saveRef); + } + return result; + + case RESOURCE_FILES: + if ((objc < 2) || (objc > 3)) { + Tcl_SetStringObj(resultPtr, + "wrong # args: should be \"resource files \ +?resourceId?\"", -1); + return TCL_ERROR; + } + + if (objc == 2) { + stringPtr = Tcl_GetStringFromObj(resourceForkList, &length); + Tcl_SetStringObj(resultPtr, stringPtr, length); + } else { + FCBPBRec fileRec; + Handle pathHandle; + short pathLength; + Str255 fileName; + + if (strcmp(Tcl_GetStringFromObj(objv[2], NULL), "ROM Map") + == 0) { + Tcl_SetStringObj(resultPtr,"no file path for ROM Map", -1); + return TCL_ERROR; + } + + resourceRef = GetRsrcRefFromObj(objv[2], 1, "files", resultPtr); + if (resourceRef == NULL) { + return TCL_ERROR; + } + + fileRec.ioCompletion = NULL; + fileRec.ioFCBIndx = 0; + fileRec.ioNamePtr = fileName; + fileRec.ioVRefNum = 0; + fileRec.ioRefNum = resourceRef->fileRef; + err = PBGetFCBInfo(&fileRec, false); + if (err != noErr) { + Tcl_SetStringObj(resultPtr, + "could not get FCB for resource file", -1); + return TCL_ERROR; + } + + err = GetFullPath(fileRec.ioFCBVRefNum, fileRec.ioFCBParID, + fileRec.ioNamePtr, &pathLength, &pathHandle); + if ( err != noErr) { + Tcl_SetStringObj(resultPtr, + "could not get file path from token", -1); + return TCL_ERROR; + } + + HLock(pathHandle); + Tcl_SetStringObj(resultPtr,*pathHandle,pathLength); + HUnlock(pathHandle); + DisposeHandle(pathHandle); + } + return TCL_OK; + case RESOURCE_LIST: + if (!((objc == 3) || (objc == 4))) { + Tcl_WrongNumArgs(interp, 2, objv, "resourceType ?resourceRef?"); + return TCL_ERROR; + } + if (Tcl_GetOSTypeFromObj(interp, objv[2], &rezType) != TCL_OK) { + return TCL_ERROR; + } + + if (objc == 4) { + resourceRef = GetRsrcRefFromObj(objv[3], 1, + "list", resultPtr); + if (resourceRef == NULL) { + return TCL_ERROR; + } + + saveRef = CurResFile(); + UseResFile((short) resourceRef->fileRef); + limitSearch = true; + } + + Tcl_ResetResult(interp); + if (limitSearch) { + count = Count1Resources(rezType); + } else { + count = CountResources(rezType); + } + SetResLoad(false); + for (i = 1; i <= count; i++) { + if (limitSearch) { + resource = Get1IndResource(rezType, i); + } else { + resource = GetIndResource(rezType, i); + } + if (resource != NULL) { + GetResInfo(resource, &id, (ResType *) &rezType, theName); + if (theName[0] != 0) { + objPtr = Tcl_NewStringObj((char *) theName + 1, + theName[0]); + } else { + objPtr = Tcl_NewIntObj(id); + } + ReleaseResource(resource); + result = Tcl_ListObjAppendElement(interp, resultPtr, + objPtr); + if (result != TCL_OK) { + Tcl_DecrRefCount(objPtr); + break; + } + } + } + SetResLoad(true); + + if (limitSearch) { + UseResFile(saveRef); + } + + return TCL_OK; + case RESOURCE_OPEN: + if (!((objc == 3) || (objc == 4))) { + Tcl_WrongNumArgs(interp, 2, objv, "fileName ?permissions?"); + return TCL_ERROR; + } + stringPtr = Tcl_GetStringFromObj(objv[2], &length); + nativeName = Tcl_TranslateFileName(interp, stringPtr, &buffer); + if (nativeName == NULL) { + return TCL_ERROR; + } + err = FSpLocationFromPath(strlen(nativeName), nativeName, + &fileSpec) ; + Tcl_DStringFree(&buffer); + if (!((err == noErr) || (err == fnfErr))) { + Tcl_AppendStringsToObj(resultPtr, + "invalid path", (char *) NULL); + return TCL_ERROR; + } + + /* + * Get permissions for the file. We really only understand + * read-only and shared-read-write. If no permissions are + * given we default to read only. + */ + + if (objc == 4) { + stringPtr = Tcl_GetStringFromObj(objv[3], &length); + mode = TclGetOpenMode(interp, stringPtr, &index); + if (mode == -1) { + /* TODO: TclGetOpenMode doesn't work with Obj commands. */ + return TCL_ERROR; + } + switch (mode & (O_RDONLY | O_WRONLY | O_RDWR)) { + case O_RDONLY: + macPermision = fsRdPerm; + break; + case O_WRONLY: + case O_RDWR: + macPermision = fsRdWrShPerm; + break; + default: + panic("Tcl_ResourceObjCmd: invalid mode value"); + break; + } + } else { + macPermision = fsRdPerm; + } + + /* + * Don't load in any of the resources in the file, this could + * cause problems if you open a file that has CODE resources... + */ + + SetResLoad(false); + fileRef = (long) FSpOpenResFileCompat(&fileSpec, macPermision); + SetResLoad(true); + + if (fileRef == -1) { + err = ResError(); + if (((err == fnfErr) || (err == eofErr)) && + (macPermision == fsRdWrShPerm)) { + /* + * No resource fork existed for this file. Since we are + * opening it for writing we will create the resource fork + * now. + */ + + HCreateResFile(fileSpec.vRefNum, fileSpec.parID, + fileSpec.name); + fileRef = (long) FSpOpenResFileCompat(&fileSpec, + macPermision); + if (fileRef == -1) { + goto openError; + } + } else if (err == fnfErr) { + Tcl_AppendStringsToObj(resultPtr, + "file does not exist", (char *) NULL); + return TCL_ERROR; + } else if (err == eofErr) { + Tcl_AppendStringsToObj(resultPtr, + "file does not contain resource fork", (char *) NULL); + return TCL_ERROR; + } else { + openError: + Tcl_AppendStringsToObj(resultPtr, + "error opening resource file", (char *) NULL); + return TCL_ERROR; + } + } + + /* + * The FspOpenResFile function does not set the ResFileAttrs. + * Even if you open the file read only, the mapReadOnly + * attribute is not set. This means we can't detect writes to a + * read only resource fork until the write fails, which is bogus. + * So set it here... + */ + + if (macPermision == fsRdPerm) { + SetResFileAttrs(fileRef, mapReadOnly); + } + + Tcl_SetStringObj(resultPtr, "", 0); + if (TclMacRegisterResourceFork(fileRef, resultPtr, + TCL_RESOURCE_CHECK_IF_OPEN) != TCL_OK) { + CloseResFile(fileRef); + return TCL_ERROR; + } + + return TCL_OK; + case RESOURCE_READ: + if (!((objc == 4) || (objc == 5))) { + Tcl_WrongNumArgs(interp, 2, objv, + "resourceType resourceId ?resourceRef?"); + return TCL_ERROR; + } + + if (Tcl_GetOSTypeFromObj(interp, objv[2], &rezType) != TCL_OK) { + return TCL_ERROR; + } + + if (Tcl_GetLongFromObj((Tcl_Interp *) NULL, objv[3], &rsrcId) + != TCL_OK) { + resourceId = Tcl_GetStringFromObj(objv[3], &length); + } + + if (objc == 5) { + stringPtr = Tcl_GetStringFromObj(objv[4], &length); + } else { + stringPtr = NULL; + } + + resource = Tcl_MacFindResource(interp, rezType, resourceId, + rsrcId, stringPtr, &releaseIt); + + if (resource != NULL) { + size = GetResourceSizeOnDisk(resource); + Tcl_SetStringObj(resultPtr, *resource, size); + + /* + * Don't release the resource unless WE loaded it... + */ + + if (releaseIt) { + ReleaseResource(resource); + } + return TCL_OK; + } else { + Tcl_AppendStringsToObj(resultPtr, "could not load resource", + (char *) NULL); + return TCL_ERROR; + } + case RESOURCE_TYPES: + if (!((objc == 2) || (objc == 3))) { + Tcl_WrongNumArgs(interp, 2, objv, "?resourceRef?"); + return TCL_ERROR; + } + + if (objc == 3) { + resourceRef = GetRsrcRefFromObj(objv[2], 1, + "get types of", resultPtr); + if (resourceRef == NULL) { + return TCL_ERROR; + } + + saveRef = CurResFile(); + UseResFile((short) resourceRef->fileRef); + limitSearch = true; + } + + if (limitSearch) { + count = Count1Types(); + } else { + count = CountTypes(); + } + for (i = 1; i <= count; i++) { + if (limitSearch) { + Get1IndType((ResType *) &rezType, i); + } else { + GetIndType((ResType *) &rezType, i); + } + objPtr = Tcl_NewOSTypeObj(rezType); + result = Tcl_ListObjAppendElement(interp, resultPtr, objPtr); + if (result != TCL_OK) { + Tcl_DecrRefCount(objPtr); + break; + } + } + + if (limitSearch) { + UseResFile(saveRef); + } + + return result; + case RESOURCE_WRITE: + if ((objc < 4) || (objc > 11)) { + Tcl_WrongNumArgs(interp, 2, objv, + "?-id resourceId? ?-name resourceName? ?-file resourceRef?\ + ?-force? resourceType data"); + return TCL_ERROR; + } + + i = 2; + gotInt = false; + resourceId = NULL; + limitSearch = false; + force = 0; + + while (i < (objc - 2)) { + if (Tcl_GetIndexFromObj(interp, objv[i], writeSwitches, + "switch", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + + switch (index) { + case RESOURCE_WRITE_ID: + if (Tcl_GetLongFromObj(interp, objv[i+1], &rsrcId) + != TCL_OK) { + return TCL_ERROR; + } + gotInt = true; + i += 2; + break; + case RESOURCE_WRITE_NAME: + resourceId = Tcl_GetStringFromObj(objv[i+1], &length); + strcpy((char *) theName, resourceId); + resourceId = (char *) theName; + c2pstr(resourceId); + i += 2; + break; + case RESOURCE_WRITE_FILE: + resourceRef = GetRsrcRefFromObj(objv[i+1], 0, + "write to", resultPtr); + if (resourceRef == NULL) { + return TCL_ERROR; + } + limitSearch = true; + i += 2; + break; + case RESOURCE_FORCE: + force = 1; + i += 1; + break; + } + } + if (Tcl_GetOSTypeFromObj(interp, objv[i], &rezType) != TCL_OK) { + return TCL_ERROR; + } + stringPtr = Tcl_GetStringFromObj(objv[i+1], &length); + + if (gotInt == false) { + rsrcId = UniqueID(rezType); + } + if (resourceId == NULL) { + resourceId = (char *) "\p"; + } + if (limitSearch) { + saveRef = CurResFile(); + UseResFile((short) resourceRef->fileRef); + } + + /* + * If we are adding the resource by number, then we must make sure + * there is not already a resource of that number. We are not going + * load it here, since we want to detect whether we loaded it or + * not. Remember that releasing some resources in particular menu + * related ones, can be fatal. + */ + + if (gotInt == true) { + SetResLoad(false); + resource = Get1Resource(rezType,rsrcId); + SetResLoad(true); + } + + if (resource == NULL) { + /* + * We get into this branch either if there was not already a + * resource of this type & id, or the id was not specified. + */ + + resource = NewHandle(length); + if (resource == NULL) { + resource = NewHandleSys(length); + if (resource == NULL) { + panic("could not allocate memory to write resource"); + } + } + HLock(resource); + memcpy(*resource, stringPtr, length); + HUnlock(resource); + AddResource(resource, rezType, (short) rsrcId, + (StringPtr) resourceId); + releaseIt = 1; + } else { + /* + * We got here because there was a resource of this type + * & ID in the file. + */ + + if (*resource == NULL) { + releaseIt = 1; + } else { + releaseIt = 0; + } + + if (!force) { + /* + *We only overwrite extant resources + * when the -force flag has been set. + */ + + sprintf(errbuf,"%d", rsrcId); + + Tcl_AppendStringsToObj(resultPtr, "the resource ", + errbuf, " already exists, use \"-force\"", + " to overwrite it.", (char *) NULL); + + result = TCL_ERROR; + goto writeDone; + } else if (GetResAttrs(resource) & resProtected) { + /* + * + * Next, check to see if it is protected... + */ + + sprintf(errbuf,"%d", rsrcId); + Tcl_AppendStringsToObj(resultPtr, + "could not write resource id ", + errbuf, " of type ", + Tcl_GetStringFromObj(objv[i],&length), + ", it was protected.",(char *) NULL); + result = TCL_ERROR; + goto writeDone; + } else { + /* + * Be careful, the resource might already be in memory + * if something else loaded it. + */ + + if (*resource == 0) { + LoadResource(resource); + err = ResError(); + if (err != noErr) { + sprintf(errbuf,"%d", rsrcId); + Tcl_AppendStringsToObj(resultPtr, + "error loading resource ", + errbuf, " of type ", + Tcl_GetStringFromObj(objv[i],&length), + " to overwrite it", (char *) NULL); + goto writeDone; + } + } + + SetHandleSize(resource, length); + if ( MemError() != noErr ) { + panic("could not allocate memory to write resource"); + } + + HLock(resource); + memcpy(*resource, stringPtr, length); + HUnlock(resource); + + ChangedResource(resource); + + /* + * We also may have changed the name... + */ + + SetResInfo(resource, rsrcId, (StringPtr) resourceId); + } + } + + err = ResError(); + if (err != noErr) { + Tcl_AppendStringsToObj(resultPtr, + "error adding resource to resource map", + (char *) NULL); + result = TCL_ERROR; + goto writeDone; + } + + WriteResource(resource); + err = ResError(); + if (err != noErr) { + Tcl_AppendStringsToObj(resultPtr, + "error writing resource to disk", + (char *) NULL); + result = TCL_ERROR; + } + + writeDone: + + if (releaseIt) { + ReleaseResource(resource); + err = ResError(); + if (err != noErr) { + Tcl_AppendStringsToObj(resultPtr, + "error releasing resource", + (char *) NULL); + result = TCL_ERROR; + } + } + + if (limitSearch) { + UseResFile(saveRef); + } + + return result; + default: + panic("Tcl_GetIndexFromObject returned unrecognized option"); + return TCL_ERROR; /* Should never be reached. */ + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_MacSourceObjCmd -- + * + * This procedure is invoked to process the "source" Tcl command. + * See the user documentation for details on what it does. In + * addition, it supports sourceing from the resource fork of + * type 'TEXT'. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_MacSourceObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *CONST objv[]) /* Argument objects. */ +{ + char *errNum = "wrong # args: "; + char *errBad = "bad argument: "; + char *errStr; + char *fileName = NULL, *rsrcName = NULL; + long rsrcID = -1; + char *string; + int length; + + if (objc < 2 || objc > 4) { + errStr = errNum; + goto sourceFmtErr; + } + + if (objc == 2) { + string = TclGetStringFromObj(objv[1], &length); + return Tcl_EvalFile(interp, string); + } + + /* + * The following code supports a few older forms of this command + * for backward compatability. + */ + string = TclGetStringFromObj(objv[1], &length); + if (!strcmp(string, "-rsrc") || !strcmp(string, "-rsrcname")) { + rsrcName = TclGetStringFromObj(objv[2], &length); + } else if (!strcmp(string, "-rsrcid")) { + if (Tcl_GetLongFromObj(interp, objv[2], &rsrcID) != TCL_OK) { + return TCL_ERROR; + } + } else { + errStr = errBad; + goto sourceFmtErr; + } + + if (objc == 4) { + fileName = TclGetStringFromObj(objv[3], &length); + } + return Tcl_MacEvalResource(interp, rsrcName, rsrcID, fileName); + + sourceFmtErr: + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), errStr, "should be \"", + Tcl_GetStringFromObj(objv[0], (int *) NULL), + " fileName\" or \"", + Tcl_GetStringFromObj(objv[0], (int *) NULL), + " -rsrc name ?fileName?\" or \"", + Tcl_GetStringFromObj(objv[0], (int *) NULL), + " -rsrcid id ?fileName?\"", (char *) NULL); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_BeepObjCmd -- + * + * This procedure makes the beep sound. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Makes a beep. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_BeepObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *CONST objv[]) /* Argument values. */ +{ + Tcl_Obj *resultPtr, *objPtr; + Handle sound; + Str255 sndName; + int volume = -1, length; + char * sndArg = NULL; + + resultPtr = Tcl_GetObjResult(interp); + if (objc == 1) { + SysBeep(1); + return TCL_OK; + } else if (objc == 2) { + if (!strcmp(Tcl_GetStringFromObj(objv[1], &length), "-list")) { + int count, i; + short id; + Str255 theName; + ResType rezType; + + count = CountResources('snd '); + for (i = 1; i <= count; i++) { + sound = GetIndResource('snd ', i); + if (sound != NULL) { + GetResInfo(sound, &id, &rezType, theName); + if (theName[0] == 0) { + continue; + } + objPtr = Tcl_NewStringObj((char *) theName + 1, + theName[0]); + Tcl_ListObjAppendElement(interp, resultPtr, objPtr); + } + } + return TCL_OK; + } else { + sndArg = Tcl_GetStringFromObj(objv[1], &length); + } + } else if (objc == 3) { + if (!strcmp(Tcl_GetStringFromObj(objv[1], &length), "-volume")) { + Tcl_GetIntFromObj(interp, objv[2], &volume); + } else { + goto beepUsage; + } + } else if (objc == 4) { + if (!strcmp(Tcl_GetStringFromObj(objv[1], &length), "-volume")) { + Tcl_GetIntFromObj(interp, objv[2], &volume); + sndArg = Tcl_GetStringFromObj(objv[3], &length); + } else { + goto beepUsage; + } + } else { + goto beepUsage; + } + + /* + * Play the sound + */ + if (sndArg == NULL) { + /* + * Set Volume for SysBeep + */ + + if (volume >= 0) { + SetSoundVolume(volume, SYS_BEEP_VOLUME); + } + SysBeep(1); + + /* + * Reset Volume + */ + + if (volume >= 0) { + SetSoundVolume(0, RESET_VOLUME); + } + } else { + strcpy((char *) sndName + 1, sndArg); + sndName[0] = length; + sound = GetNamedResource('snd ', sndName); + if (sound != NULL) { + /* + * Set Volume for Default Output device + */ + + if (volume >= 0) { + SetSoundVolume(volume, DEFAULT_SND_VOLUME); + } + + SndPlay(NULL, (SndListHandle) sound, false); + + /* + * Reset Volume + */ + + if (volume >= 0) { + SetSoundVolume(0, RESET_VOLUME); + } + } else { + Tcl_AppendStringsToObj(resultPtr, " \"", sndArg, + "\" is not a valid sound. (Try ", + Tcl_GetStringFromObj(objv[0], (int *) NULL), + " -list)", NULL); + return TCL_ERROR; + } + } + + return TCL_OK; + + beepUsage: + Tcl_WrongNumArgs(interp, 1, objv, "[-volume num] [-list | sndName]?"); + return TCL_ERROR; +} + +/* + *----------------------------------------------------------------------------- + * + * SetSoundVolume -- + * + * Set the volume for either the SysBeep or the SndPlay call depending + * on the value of mode (SYS_BEEP_VOLUME or DEFAULT_SND_VOLUME + * respectively. + * + * It also stores the last channel set, and the old value of its + * VOLUME. If you call SetSoundVolume with a mode of RESET_VOLUME, + * it will undo the last setting. The volume parameter is + * ignored in this case. + * + * Side Effects: + * Sets the System Volume + * + * Results: + * None + * + *----------------------------------------------------------------------------- + */ + +void +SetSoundVolume( + int volume, /* This is the new volume */ + enum WhichVolume mode) /* This flag says which volume to + * set: SysBeep, SndPlay, or instructs us + * to reset the volume */ +{ + static int hasSM3 = -1; + static enum WhichVolume oldMode; + static long oldVolume = -1; + + /* + * The volume setting calls only work if we have SoundManager + * 3.0 or higher. So we check that here. + */ + + if (hasSM3 == -1) { + if (GetToolboxTrapAddress(_SoundDispatch) + != GetToolboxTrapAddress(_Unimplemented)) { + NumVersion SMVers = SndSoundManagerVersion(); + if (SMVers.majorRev > 2) { + hasSM3 = 1; + } else { + hasSM3 = 0; + } + } else { + /* + * If the SoundDispatch trap is not present, then + * we don't have the SoundManager at all. + */ + + hasSM3 = 0; + } + } + + /* + * If we don't have Sound Manager 3.0, we can't set the sound volume. + * We will just ignore the request rather than raising an error. + */ + + if (!hasSM3) { + return; + } + + switch (mode) { + case SYS_BEEP_VOLUME: + GetSysBeepVolume(&oldVolume); + SetSysBeepVolume(volume); + oldMode = SYS_BEEP_VOLUME; + break; + case DEFAULT_SND_VOLUME: + GetDefaultOutputVolume(&oldVolume); + SetDefaultOutputVolume(volume); + oldMode = DEFAULT_SND_VOLUME; + break; + case RESET_VOLUME: + /* + * If oldVolume is -1 someone has made a programming error + * and called reset before setting the volume. This is benign + * however, so we will just exit. + */ + + if (oldVolume != -1) { + if (oldMode == SYS_BEEP_VOLUME) { + SetSysBeepVolume(oldVolume); + } else if (oldMode == DEFAULT_SND_VOLUME) { + SetDefaultOutputVolume(oldVolume); + } + } + oldVolume = -1; + } +} + +/* + *----------------------------------------------------------------------------- + * + * Tcl_MacEvalResource -- + * + * Used to extend the source command. Sources Tcl code from a Text + * resource. Currently only sources the resouce by name file ID may be + * supported at a later date. + * + * Side Effects: + * Depends on the Tcl code in the resource. + * + * Results: + * Returns a Tcl result. + * + *----------------------------------------------------------------------------- + */ + +int +Tcl_MacEvalResource( + Tcl_Interp *interp, /* Interpreter in which to process file. */ + char *resourceName, /* Name of TEXT resource to source, + NULL if number should be used. */ + int resourceNumber, /* Resource id of source. */ + char *fileName) /* Name of file to process. + NULL if application resource. */ +{ + Handle sourceText; + Str255 rezName; + char msg[200]; + int result, iOpenedResFile = false; + short saveRef, fileRef = -1; + char idStr[64]; + FSSpec fileSpec; + Tcl_DString buffer; + char *nativeName; + + saveRef = CurResFile(); + + if (fileName != NULL) { + OSErr err; + + nativeName = Tcl_TranslateFileName(interp, fileName, &buffer); + if (nativeName == NULL) { + return TCL_ERROR; + } + err = FSpLocationFromPath(strlen(nativeName), nativeName, + &fileSpec); + Tcl_DStringFree(&buffer); + if (err != noErr) { + Tcl_AppendResult(interp, "Error finding the file: \"", + fileName, "\".", NULL); + return TCL_ERROR; + } + + fileRef = FSpOpenResFileCompat(&fileSpec, fsRdPerm); + if (fileRef == -1) { + Tcl_AppendResult(interp, "Error reading the file: \"", + fileName, "\".", NULL); + return TCL_ERROR; + } + + UseResFile(fileRef); + iOpenedResFile = true; + } else { + /* + * The default behavior will search through all open resource files. + * This may not be the behavior you desire. If you want the behavior + * of this call to *only* search the application resource fork, you + * must call UseResFile at this point to set it to the application + * file. This means you must have already obtained the application's + * fileRef when the application started up. + */ + } + + /* + * Load the resource by name or ID + */ + if (resourceName != NULL) { + strcpy((char *) rezName + 1, resourceName); + rezName[0] = strlen(resourceName); + sourceText = GetNamedResource('TEXT', rezName); + } else { + sourceText = GetResource('TEXT', (short) resourceNumber); + } + + if (sourceText == NULL) { + result = TCL_ERROR; + } else { + char *sourceStr = NULL; + + HLock(sourceText); + sourceStr = Tcl_MacConvertTextResource(sourceText); + HUnlock(sourceText); + ReleaseResource(sourceText); + + /* + * We now evaluate the Tcl source + */ + result = Tcl_Eval(interp, sourceStr); + ckfree(sourceStr); + if (result == TCL_RETURN) { + result = TCL_OK; + } else if (result == TCL_ERROR) { + sprintf(msg, "\n (rsrc \"%.150s\" line %d)", + resourceName, + interp->errorLine); + Tcl_AddErrorInfo(interp, msg); + } + + goto rezEvalCleanUp; + } + + rezEvalError: + sprintf(idStr, "ID=%d", resourceNumber); + Tcl_AppendResult(interp, "The resource \"", + (resourceName != NULL ? resourceName : idStr), + "\" could not be loaded from ", + (fileName != NULL ? fileName : "application"), + ".", NULL); + + rezEvalCleanUp: + + /* + * TRICKY POINT: The code that you are sourcing here could load a + * shared library. This will go AHEAD of the resource we stored away + * in saveRef on the resource path. + * If you restore the saveRef in this case, you will never be able + * to get to the resources in the shared library, since you are now + * pointing too far down on the resource list. + * So, we only reset the current resource file if WE opened a resource + * explicitly, and then only if the CurResFile is still the + * one we opened... + */ + + if (iOpenedResFile && (CurResFile() == fileRef)) { + UseResFile(saveRef); + } + + if (fileRef != -1) { + CloseResFile(fileRef); + } + + return result; +} + +/* + *----------------------------------------------------------------------------- + * + * Tcl_MacConvertTextResource -- + * + * Converts a TEXT resource into a Tcl suitable string. + * + * Side Effects: + * Mallocs the returned memory, converts '\r' to '\n', and appends a NULL. + * + * Results: + * A new malloced string. + * + *----------------------------------------------------------------------------- + */ + +char * +Tcl_MacConvertTextResource( + Handle resource) /* Handle to TEXT resource. */ +{ + int i, size; + char *resultStr; + + size = GetResourceSizeOnDisk(resource); + + resultStr = ckalloc(size + 1); + + for (i=0; i<size; i++) { + if ((*resource)[i] == '\r') { + resultStr[i] = '\n'; + } else { + resultStr[i] = (*resource)[i]; + } + } + + resultStr[size] = '\0'; + + return resultStr; +} + +/* + *----------------------------------------------------------------------------- + * + * Tcl_MacFindResource -- + * + * Higher level interface for loading resources. + * + * Side Effects: + * Attempts to load a resource. + * + * Results: + * A handle on success. + * + *----------------------------------------------------------------------------- + */ + +Handle +Tcl_MacFindResource( + Tcl_Interp *interp, /* Interpreter in which to process file. */ + long resourceType, /* Type of resource to load. */ + char *resourceName, /* Name of resource to find, + * NULL if number should be used. */ + int resourceNumber, /* Resource id of source. */ + char *resFileRef, /* Registered resource file reference, + * NULL if searching all open resource files. */ + int *releaseIt) /* Should we release this resource when done. */ +{ + Tcl_HashEntry *nameHashPtr; + OpenResourceFork *resourceRef; + int limitSearch = false; + short saveRef; + Handle resource; + + if (resFileRef != NULL) { + nameHashPtr = Tcl_FindHashEntry(&nameTable, resFileRef); + if (nameHashPtr == NULL) { + Tcl_AppendResult(interp, "invalid resource file reference \"", + resFileRef, "\"", (char *) NULL); + return NULL; + } + resourceRef = (OpenResourceFork *) Tcl_GetHashValue(nameHashPtr); + saveRef = CurResFile(); + UseResFile((short) resourceRef->fileRef); + limitSearch = true; + } + + /* + * Some system resources (for example system resources) should not + * be released. So we set autoload to false, and try to get the resource. + * If the Master Pointer of the returned handle is null, then resource was + * not in memory, and it is safe to release it. Otherwise, it is not. + */ + + SetResLoad(false); + + if (resourceName == NULL) { + if (limitSearch) { + resource = Get1Resource(resourceType, resourceNumber); + } else { + resource = GetResource(resourceType, resourceNumber); + } + } else { + c2pstr(resourceName); + if (limitSearch) { + resource = Get1NamedResource(resourceType, + (StringPtr) resourceName); + } else { + resource = GetNamedResource(resourceType, + (StringPtr) resourceName); + } + p2cstr((StringPtr) resourceName); + } + + if (*resource == NULL) { + *releaseIt = 1; + LoadResource(resource); + } else { + *releaseIt = 0; + } + + SetResLoad(true); + + + if (limitSearch) { + UseResFile(saveRef); + } + + return resource; +} + +/* + *---------------------------------------------------------------------- + * + * ResourceInit -- + * + * Initialize the structures used for resource management. + * + * Results: + * None. + * + * Side effects: + * Read the code. + * + *---------------------------------------------------------------------- + */ + +static void +ResourceInit() +{ + + initialized = 1; + Tcl_InitHashTable(&nameTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&resourceTable, TCL_ONE_WORD_KEYS); + resourceForkList = Tcl_NewObj(); + Tcl_IncrRefCount(resourceForkList); + + BuildResourceForkList(); + +} +/***/ + +/*Tcl_RegisterObjType(typePtr) */ + +/* + *---------------------------------------------------------------------- + * + * Tcl_NewOSTypeObj -- + * + * This procedure is used to create a new resource name type object. + * + * Results: + * The newly created object is returned. This object will have a NULL + * string representation. The returned object has ref count 0. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +Tcl_NewOSTypeObj( + OSType newOSType) /* Int used to initialize the new object. */ +{ + register Tcl_Obj *objPtr; + + if (!osTypeInit) { + osTypeInit = 1; + Tcl_RegisterObjType(&osType); + } + + objPtr = Tcl_NewObj(); + objPtr->bytes = NULL; + objPtr->internalRep.longValue = newOSType; + objPtr->typePtr = &osType; + return objPtr; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_SetOSTypeObj -- + * + * Modify an object to be a resource type and to have the + * specified long value. + * + * Results: + * None. + * + * Side effects: + * The object's old string rep, if any, is freed. Also, any old + * internal rep is freed. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_SetOSTypeObj( + Tcl_Obj *objPtr, /* Object whose internal rep to init. */ + OSType newOSType) /* Integer used to set object's value. */ +{ + register Tcl_ObjType *oldTypePtr = objPtr->typePtr; + + if (!osTypeInit) { + osTypeInit = 1; + Tcl_RegisterObjType(&osType); + } + + Tcl_InvalidateStringRep(objPtr); + if ((oldTypePtr != NULL) && (oldTypePtr->freeIntRepProc != NULL)) { + oldTypePtr->freeIntRepProc(objPtr); + } + + objPtr->internalRep.longValue = newOSType; + objPtr->typePtr = &osType; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_GetOSTypeFromObj -- + * + * Attempt to return an int from the Tcl object "objPtr". If the object + * is not already an int, an attempt will be made to convert it to one. + * + * Results: + * The return value is a standard Tcl object result. If an error occurs + * during conversion, an error message is left in interp->objResult + * unless "interp" is NULL. + * + * Side effects: + * If the object is not already an int, the conversion will free + * any old internal representation. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_GetOSTypeFromObj( + Tcl_Interp *interp, /* Used for error reporting if not NULL. */ + Tcl_Obj *objPtr, /* The object from which to get a int. */ + OSType *osTypePtr) /* Place to store resulting int. */ +{ + register int result; + + if (!osTypeInit) { + osTypeInit = 1; + Tcl_RegisterObjType(&osType); + } + + if (objPtr->typePtr == &osType) { + *osTypePtr = objPtr->internalRep.longValue; + return TCL_OK; + } + + result = SetOSTypeFromAny(interp, objPtr); + if (result == TCL_OK) { + *osTypePtr = objPtr->internalRep.longValue; + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * DupOSTypeInternalRep -- + * + * Initialize the internal representation of an int Tcl_Obj to a + * copy of the internal representation of an existing int object. + * + * Results: + * None. + * + * Side effects: + * "copyPtr"s internal rep is set to the integer corresponding to + * "srcPtr"s internal rep. + * + *---------------------------------------------------------------------- + */ + +static void +DupOSTypeInternalRep( + Tcl_Obj *srcPtr, /* Object with internal rep to copy. */ + Tcl_Obj *copyPtr) /* Object with internal rep to set. */ +{ + copyPtr->internalRep.longValue = srcPtr->internalRep.longValue; + copyPtr->typePtr = &osType; +} + +/* + *---------------------------------------------------------------------- + * + * SetOSTypeFromAny -- + * + * Attempt to generate an integer internal form for the Tcl object + * "objPtr". + * + * Results: + * The return value is a standard object Tcl result. If an error occurs + * during conversion, an error message is left in interp->objResult + * unless "interp" is NULL. + * + * Side effects: + * If no error occurs, an int is stored as "objPtr"s internal + * representation. + * + *---------------------------------------------------------------------- + */ + +static int +SetOSTypeFromAny( + Tcl_Interp *interp, /* Used for error reporting if not NULL. */ + Tcl_Obj *objPtr) /* The object to convert. */ +{ + Tcl_ObjType *oldTypePtr = objPtr->typePtr; + char *string; + int length; + long newOSType; + + /* + * Get the string representation. Make it up-to-date if necessary. + */ + + string = TclGetStringFromObj(objPtr, &length); + + if (length != 4) { + if (interp != NULL) { + Tcl_ResetResult(interp); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "expected Macintosh OS type but got \"", string, "\"", + (char *) NULL); + } + return TCL_ERROR; + } + newOSType = *((long *) string); + + /* + * The conversion to resource type succeeded. Free the old internalRep + * before setting the new one. + */ + + if ((oldTypePtr != NULL) && (oldTypePtr->freeIntRepProc != NULL)) { + oldTypePtr->freeIntRepProc(objPtr); + } + + objPtr->internalRep.longValue = newOSType; + objPtr->typePtr = &osType; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * UpdateStringOfOSType -- + * + * Update the string representation for an resource type object. + * Note: This procedure does not free an existing old string rep + * so storage will be lost if this has not already been done. + * + * Results: + * None. + * + * Side effects: + * The object's string is set to a valid string that results from + * the int-to-string conversion. + * + *---------------------------------------------------------------------- + */ + +static void +UpdateStringOfOSType( + register Tcl_Obj *objPtr) /* Int object whose string rep to update. */ +{ + objPtr->bytes = ckalloc(5); + sprintf(objPtr->bytes, "%-4.4s", &(objPtr->internalRep.longValue)); + objPtr->length = 4; +} + +/* + *---------------------------------------------------------------------- + * + * GetRsrcRefFromObj -- + * + * Given a String object containing a resource file token, return + * the OpenResourceFork structure that it represents, or NULL if + * the token cannot be found. If okayOnReadOnly is false, it will + * also check whether the token corresponds to a read-only file, + * and return NULL if it is. + * + * Results: + * A pointer to an OpenResourceFork structure, or NULL. + * + * Side effects: + * An error message may be left in resultPtr. + * + *---------------------------------------------------------------------- + */ + +static OpenResourceFork * +GetRsrcRefFromObj( + register Tcl_Obj *objPtr, /* String obj containing file token */ + int okayOnReadOnly, /* Whether this operation is okay for a * + * read only file. */ + const char *operation, /* String containing the operation we * + * were trying to perform, used for errors */ + Tcl_Obj *resultPtr) /* Tcl_Obj to contain error message */ +{ + char *stringPtr; + Tcl_HashEntry *nameHashPtr; + OpenResourceFork *resourceRef; + int length; + OSErr err; + + stringPtr = Tcl_GetStringFromObj(objPtr, &length); + nameHashPtr = Tcl_FindHashEntry(&nameTable, stringPtr); + if (nameHashPtr == NULL) { + Tcl_AppendStringsToObj(resultPtr, + "invalid resource file reference \"", + stringPtr, "\"", (char *) NULL); + return NULL; + } + + resourceRef = (OpenResourceFork *) Tcl_GetHashValue(nameHashPtr); + + if (!okayOnReadOnly) { + err = GetResFileAttrs((short) resourceRef->fileRef); + if (err & mapReadOnly) { + Tcl_AppendStringsToObj(resultPtr, "cannot ", operation, + " resource file \"", + stringPtr, "\", it was opened read only", + (char *) NULL); + return NULL; + } + } + return resourceRef; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacRegisterResourceFork -- + * + * Register an open resource fork in the table of open resources + * managed by the procedures in this file. If the resource file + * is already registered with the table, then no new token is made. + * + * The behavior is controlled by the value of tokenPtr, and of the + * flags variable. For tokenPtr, the possibilities are: + * - NULL: The new token is auto-generated, but not returned. + * - The string value of tokenPtr is the empty string: Then + * the new token is auto-generated, and returned in tokenPtr + * - tokenPtr has a value: The string value will be used for the token, + * unless it is already in use, in which case a new token will + * be generated, and returned in tokenPtr. + * + * For the flags variable: it can be one of: + * - TCL_RESOURCE__INSERT_TAIL: The element is inserted at the + * end of the list of open resources. Used only in Resource_Init. + * - TCL_RESOURCE_DONT_CLOSE: The resource close command will not close + * this resource. + * - TCL_RESOURCE_CHECK_IF_OPEN: This will check to see if this file's + * resource fork is already opened by this Tcl shell, and return + * an error without registering the resource fork. + * + * Results: + * Standard Tcl Result + * + * Side effects: + * An entry may be added to the resource name table. + * + *---------------------------------------------------------------------- + */ + +int +TclMacRegisterResourceFork( + short fileRef, /* File ref for an open resource fork. */ + Tcl_Obj *tokenPtr, /* A Tcl Object to which to write the * + * new token */ + int flags) /* 1 means insert at the head of the resource + * fork list, 0 means at the tail */ + +{ + Tcl_HashEntry *resourceHashPtr; + Tcl_HashEntry *nameHashPtr; + OpenResourceFork *resourceRef; + int new; + char *resourceId = NULL; + + if (!initialized) { + ResourceInit(); + } + + /* + * If we were asked to, check that this file has not been opened + * already with a different permission. It it has, then return an error. + */ + + new = 1; + + if (flags & TCL_RESOURCE_CHECK_IF_OPEN) { + Tcl_HashSearch search; + short oldFileRef, filePermissionFlag; + FCBPBRec newFileRec, oldFileRec; + OSErr err; + + oldFileRec.ioCompletion = NULL; + oldFileRec.ioFCBIndx = 0; + oldFileRec.ioNamePtr = NULL; + + newFileRec.ioCompletion = NULL; + newFileRec.ioFCBIndx = 0; + newFileRec.ioNamePtr = NULL; + newFileRec.ioVRefNum = 0; + newFileRec.ioRefNum = fileRef; + err = PBGetFCBInfo(&newFileRec, false); + filePermissionFlag = ( newFileRec.ioFCBFlags >> 12 ) & 0x1; + + + resourceHashPtr = Tcl_FirstHashEntry(&resourceTable, &search); + while (resourceHashPtr != NULL) { + oldFileRef = (short) Tcl_GetHashKey(&resourceTable, + resourceHashPtr); + if (oldFileRef == fileRef) { + new = 0; + break; + } + oldFileRec.ioVRefNum = 0; + oldFileRec.ioRefNum = oldFileRef; + err = PBGetFCBInfo(&oldFileRec, false); + + /* + * err might not be noErr either because the file has closed + * out from under us somehow, which is bad but we're not going + * to fix it here, OR because it is the ROM MAP, which has a + * fileRef, but can't be gotten to by PBGetFCBInfo. + */ + + if ((err == noErr) + && (newFileRec.ioFCBVRefNum == oldFileRec.ioFCBVRefNum) + && (newFileRec.ioFCBFlNm == oldFileRec.ioFCBFlNm)) { + /* In MacOS 8.1 it seems like we get different file refs even though + * we pass the same file & permissions. This is not what Inside Mac + * says should happen, but it does, so if it does, then close the new res + * file and return the original one... + */ + + if (filePermissionFlag == ((oldFileRec.ioFCBFlags >> 12) & 0x1)) { + CloseResFile(fileRef); + new = 0; + break; + } else { + if (tokenPtr != NULL) { + Tcl_SetStringObj(tokenPtr, + "Resource already open with different permissions.", -1); + } + return TCL_ERROR; + } + } + resourceHashPtr = Tcl_NextHashEntry(&search); + } + } + + + /* + * If the file has already been opened with these same permissions, then it + * will be in our list and we will have set new to 0 above. + * So we will just return the token (if tokenPtr is non-null) + */ + + if (new) { + resourceHashPtr = Tcl_CreateHashEntry(&resourceTable, + (char *) fileRef, &new); + } + + if (!new) { + if (tokenPtr != NULL) { + resourceId = (char *) Tcl_GetHashValue(resourceHashPtr); + Tcl_SetStringObj(tokenPtr, resourceId, -1); + } + return TCL_OK; + } + + /* + * If we were passed in a result pointer which is not an empty + * string, attempt to use that as the key. If the key already + * exists, silently fall back on resource%d... + */ + + if (tokenPtr != NULL) { + char *tokenVal; + int length; + tokenVal = (char *) Tcl_GetStringFromObj(tokenPtr, &length); + if (length > 0) { + nameHashPtr = Tcl_FindHashEntry(&nameTable, tokenVal); + if (nameHashPtr == NULL) { + resourceId = ckalloc(length + 1); + memcpy(resourceId, tokenVal, length); + resourceId[length] = '\0'; + } + } + } + + if (resourceId == NULL) { + resourceId = (char *) ckalloc(15); + sprintf(resourceId, "resource%d", newId); + } + + Tcl_SetHashValue(resourceHashPtr, resourceId); + newId++; + + nameHashPtr = Tcl_CreateHashEntry(&nameTable, resourceId, &new); + if (!new) { + panic("resource id has repeated itself"); + } + + resourceRef = (OpenResourceFork *) ckalloc(sizeof(OpenResourceFork)); + resourceRef->fileRef = fileRef; + resourceRef->flags = flags; + + Tcl_SetHashValue(nameHashPtr, (ClientData) resourceRef); + if (tokenPtr != NULL) { + Tcl_SetStringObj(tokenPtr, resourceId, -1); + } + + if (flags & TCL_RESOURCE_INSERT_TAIL) { + Tcl_ListObjAppendElement(NULL, resourceForkList, tokenPtr); + } else { + Tcl_ListObjReplace(NULL, resourceForkList, 0, 0, 1, &tokenPtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacUnRegisterResourceFork -- + * + * Removes the entry for an open resource fork from the table of + * open resources managed by the procedures in this file. + * If resultPtr is not NULL, it will be used for error reporting. + * + * Results: + * The fileRef for this token, or -1 if an error occured. + * + * Side effects: + * An entry is removed from the resource name table. + * + *---------------------------------------------------------------------- + */ + +short +TclMacUnRegisterResourceFork( + char *tokenPtr, + Tcl_Obj *resultPtr) + +{ + Tcl_HashEntry *resourceHashPtr; + Tcl_HashEntry *nameHashPtr; + OpenResourceFork *resourceRef; + char *resourceId = NULL; + short fileRef; + char *bytes; + int i, match, index, listLen, length, elemLen; + Tcl_Obj **elemPtrs; + + + nameHashPtr = Tcl_FindHashEntry(&nameTable, tokenPtr); + if (nameHashPtr == NULL) { + if (resultPtr != NULL) { + Tcl_AppendStringsToObj(resultPtr, + "invalid resource file reference \"", + tokenPtr, "\"", (char *) NULL); + } + return -1; + } + + resourceRef = (OpenResourceFork *) Tcl_GetHashValue(nameHashPtr); + fileRef = resourceRef->fileRef; + + if ( resourceRef->flags & TCL_RESOURCE_DONT_CLOSE ) { + if (resultPtr != NULL) { + Tcl_AppendStringsToObj(resultPtr, + "can't close \"", tokenPtr, "\" resource file", + (char *) NULL); + } + return -1; + } + + Tcl_DeleteHashEntry(nameHashPtr); + ckfree((char *) resourceRef); + + + /* + * Now remove the resource from the resourceForkList object + */ + + Tcl_ListObjGetElements(NULL, resourceForkList, &listLen, &elemPtrs); + + + index = -1; + length = strlen(tokenPtr); + + for (i = 0; i < listLen; i++) { + match = 0; + bytes = Tcl_GetStringFromObj(elemPtrs[i], &elemLen); + if (length == elemLen) { + match = (memcmp(bytes, tokenPtr, + (size_t) length) == 0); + } + if (match) { + index = i; + break; + } + } + if (!match) { + panic("the resource Fork List is out of synch!"); + } + + Tcl_ListObjReplace(NULL, resourceForkList, index, 1, 0, NULL); + + resourceHashPtr = Tcl_FindHashEntry(&resourceTable, (char *) fileRef); + + if (resourceHashPtr == NULL) { + panic("Resource & Name tables are out of synch in resource command."); + } + ckfree(Tcl_GetHashValue(resourceHashPtr)); + Tcl_DeleteHashEntry(resourceHashPtr); + + return fileRef; + +} + + +/* + *---------------------------------------------------------------------- + * + * BuildResourceForkList -- + * + * Traverses the list of open resource forks, and builds the + * list of resources forks. Also creates a resource token for any that + * are opened but not registered with our resource system. + * This is based on code from Apple DTS. + * + * Results: + * None. + * + * Side effects: + * The list of resource forks is updated. + * The resource name table may be augmented. + * + *---------------------------------------------------------------------- + */ + +void +BuildResourceForkList() +{ + Handle currentMapHandle, mSysMapHandle; + Ptr tempPtr; + FCBPBRec fileRec; + char fileName[256]; + char appName[62]; + Tcl_Obj *nameObj; + OSErr err; + ProcessSerialNumber psn; + ProcessInfoRec info; + FSSpec fileSpec; + + /* + * Get the application name, so we can substitute + * the token "application" for the application's resource. + */ + + GetCurrentProcess(&psn); + info.processInfoLength = sizeof(ProcessInfoRec); + info.processName = (StringPtr) &appName; + info.processAppSpec = &fileSpec; + GetProcessInformation(&psn, &info); + p2cstr((StringPtr) appName); + + + fileRec.ioCompletion = NULL; + fileRec.ioVRefNum = 0; + fileRec.ioFCBIndx = 0; + fileRec.ioNamePtr = (StringPtr) &fileName; + + + currentMapHandle = LMGetTopMapHndl(); + mSysMapHandle = LMGetSysMapHndl(); + + while (1) { + /* + * Now do the ones opened after the application. + */ + + nameObj = Tcl_NewObj(); + + tempPtr = *currentMapHandle; + + fileRec.ioRefNum = *((short *) (tempPtr + 20)); + err = PBGetFCBInfo(&fileRec, false); + + if (err != noErr) { + /* + * The ROM resource map does not correspond to an opened file... + */ + Tcl_SetStringObj(nameObj, "ROM Map", -1); + } else { + p2cstr((StringPtr) fileName); + if (strcmp(fileName,(char *) appName) == 0) { + Tcl_SetStringObj(nameObj, "application", -1); + } else { + Tcl_SetStringObj(nameObj, fileName, -1); + } + c2pstr(fileName); + } + + TclMacRegisterResourceFork(fileRec.ioRefNum, nameObj, + TCL_RESOURCE_DONT_CLOSE | TCL_RESOURCE_INSERT_TAIL); + + if (currentMapHandle == mSysMapHandle) { + break; + } + + currentMapHandle = *((Handle *) (tempPtr + 16)); + } +} diff --git a/tcl/mac/tclMacResource.r b/tcl/mac/tclMacResource.r new file mode 100644 index 00000000000..4bde129b4e9 --- /dev/null +++ b/tcl/mac/tclMacResource.r @@ -0,0 +1,92 @@ +/* + * tclMacResource.r -- + * + * This file creates resources for use in a simple shell. + * This is designed to be an example of using the Tcl libraries + * statically in a Macintosh Application. For an example of + * of using the dynamic libraries look at tclMacApplication.r. + * + * Copyright (c) 1993-94 Lockheed Missle & Space Company + * Copyright (c) 1994-97 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <Types.r> +#include <SysTypes.r> + +/* + * The folowing include and defines help construct + * the version string for Tcl. + */ + +#define RESOURCE_INCLUDED +#include "tcl.h" + +#if (TCL_RELEASE_LEVEL == 0) +# define RELEASE_LEVEL alpha +#elif (TCL_RELEASE_LEVEL == 1) +# define RELEASE_LEVEL beta +#elif (TCL_RELEASE_LEVEL == 2) +# define RELEASE_LEVEL final +#endif + +#if (TCL_RELEASE_LEVEL == 2) +# define MINOR_VERSION (TCL_MINOR_VERSION * 16) + TCL_RELEASE_SERIAL +#else +# define MINOR_VERSION TCL_MINOR_VERSION * 16 +#endif + +resource 'vers' (1) { + TCL_MAJOR_VERSION, MINOR_VERSION, + RELEASE_LEVEL, 0x00, verUS, + TCL_PATCH_LEVEL, + TCL_PATCH_LEVEL ", by Ray Johnson © Sun Microsystems" +}; + +resource 'vers' (2) { + TCL_MAJOR_VERSION, MINOR_VERSION, + RELEASE_LEVEL, 0x00, verUS, + TCL_PATCH_LEVEL, + "Simple Tcl Shell " TCL_PATCH_LEVEL " © 1996" +}; + + +/* + * The mechanisim below loads Tcl source into the resource fork of the + * application. The example below creates a TEXT resource named + * "Init" from the file "init.tcl". This allows applications to use + * Tcl to define the behavior of the application without having to + * require some predetermined file structure - all needed Tcl "files" + * are located within the application. To source a file for the + * resource fork the source command has been modified to support + * sourcing from resources. In the below case "source -rsrc {Init}" + * will load the TEXT resource named "Init". + */ + +read 'TEXT' (0, "Init", purgeable, preload) "::library:init.tcl"; +read 'TEXT' (1, "History", purgeable,preload) "::library:history.tcl"; +read 'TEXT' (2, "Word", purgeable,preload) "::library:word.tcl"; + +/* + * The following resource is used when creating the 'env' variable in + * the Macintosh environment. The creation mechanisim looks for the + * 'STR#' resource named "Tcl Environment Variables" rather than a + * specific resource number. (In other words, feel free to change the + * resource id if it conflicts with your application.) Each string in + * the resource must be of the form "KEYWORD=SOME STRING". See Tcl + * documentation for futher information about the env variable. + * + * A good example of something you may want to set is: "TCL_LIBRARY=My + * disk:etc." + */ + +resource 'STR#' (128, "Tcl Environment Variables") { + { "SCHEDULE_NAME=Agent Controller Schedule", + "SCHEDULE_PATH=Lozoya:System Folder:Tcl Lib:Tcl-Scheduler" + }; +}; + diff --git a/tcl/mac/tclMacShLib.exp b/tcl/mac/tclMacShLib.exp new file mode 100644 index 00000000000..89361149844 --- /dev/null +++ b/tcl/mac/tclMacShLib.exp @@ -0,0 +1,1069 @@ +AddrToName +AddrToStr +BuildAFPVolMountInfo +BumpDate +ChangeCreatorType +ChangeFDFlags +CheckObjectLock +CheckVolLock +ClearHasBeenInited +ClearHasCustomIcon +ClearIsInvisible +ClearIsStationery +ClearNameLocked +CloseResolver +ConfigureMemory +CopyDirectoryAccess +CopyFileMgrAttributes +CopyFork +CreateFileIDRef +DTCopyComment +DTGetIcon +DTOpen +DTSetComment +DeleteDirectory +DeleteDirectoryContents +DeleteFileIDRef +DetermineVRefNum +DirectoryCopy +EnumCache +##EnvStr +ExchangeFiles +FSMakeFSSpecCompat +FSReadNoCache +FSWriteNoCache +FSWriteVerify +FSpBumpDate +FSpCatMoveCompat +FSpChangeCreatorType +FSpChangeFDFlags +FSpCheckObjectLock +FSpClearHasBeenInited +FSpClearHasCustomIcon +FSpClearIsInvisible +FSpClearIsStationery +FSpClearNameLocked +FSpCopyDirectoryAccess +FSpCopyFile +FSpCopyFileMgrAttributes +FSpCreateCompat +FSpCreateFileIDRef +FSpCreateMinimum +FSpCreateResFileCompat +FSpDTCopyComment +FSpDTSetComment +FSpDeleteCompat +FSpDirCreateCompat +FSpDirectoryCopy +FSpExchangeFilesCompat +FSpFileCopy +FSpFilteredDirectoryCopy +FSpFindFolder +FSpGetDInfo +FSpGetDefaultDir +FSpGetDirAccess +FSpGetDirectoryID +FSpGetFInfoCompat +FSpGetFLockCompat +FSpGetFileLocation +FSpGetFileSize +FSpGetForeignPrivs +FSpGetFullPath +FSpGetIOACUser +FSpLocationFromFullPath +FSpLocationFromPath +FSpMoveRename +FSpMoveRenameCompat +FSpOpenAware +FSpOpenDFCompat +FSpOpenRFAware +FSpOpenRFCompat +FSpOpenResFileCompat +FSpPathFromLocation +FSpRenameCompat +FSpResolveFileIDRef +FSpRstFLockCompat +FSpSetDInfo +FSpSetDefaultDir +FSpSetDirAccess +FSpSetFInfoCompat +FSpSetFLockCompat +FSpSetForeignPrivs +FSpSetHasCustomIcon +FSpSetIsInvisible +FSpSetIsStationery +FSpSetNameLocked +FSpShare +FSpUnshare +FileCopy +FilteredDirectoryCopy +FindDrive +FlushFile +FreeAllMemory +GetCPanelFolder +GetCatInfoNoName +GetDInfo +GetDirItems +GetDirName +GetDirectoryID +GetDiskBlocks +GetDriverName +GetFileLocation +GetFileSize +GetFilenameFromPathname +GetForeignPrivs +GetFullPath +GetGlobalMouse +GetIOACUser +GetObjectLocation +GetParentID +GetSystemFolder +GetTempBuffer +GetTrapType +GetUGEntries +GetUGEntry +GetVolMountInfo +GetVolMountInfoSize +GetVolumeInfoNoName +HCopyFile +HCreateMinimum +HGetDirAccess +HGetLogInInfo +HGetVInfo +HGetVolParms +HInfo +HMapID +HMapName +HMoveRename +HMoveRenameCompat +HOpenAware +HOpenRFAware +hypotd +HSetDirAccess +InstallConsole +LocationFromFullPath +LockRange +MXInfo +NumToolboxTraps +OnLine +OpenOurRF +OpenResolver +PBXGetVolInfoSync +ReadCharsFromConsole +RemoveConsole +ResolveFileIDRef +RestoreDefault +RetrieveAFPVolMountInfo +SIOUXBigRect +SIOUXCantSaveAlert +SIOUXDoAboutBox +SIOUXDoContentClick +SIOUXDoEditClear +SIOUXDoEditCopy +SIOUXDoEditCut +SIOUXDoEditPaste +SIOUXDoEditSelectAll +SIOUXDoMenuChoice +SIOUXDoPageSetup +SIOUXDoPrintText +SIOUXDoSaveText +SIOUXDragRect +SIOUXDrawGrowBox +SIOUXHandleOneEvent +SIOUXIsAppWindow +SIOUXMyGrowWindow +SIOUXQuitting +SIOUXSetTitle +SIOUXSettings +SIOUXSetupMenus +SIOUXSetupTextWindow +SIOUXState +SIOUXTextWindow +SIOUXUpdateMenuItems +SIOUXUpdateScrollbar +SIOUXUpdateStatusLine +SIOUXUpdateWindow +SIOUXUseWaitNextEvent +SIOUXYesNoCancelAlert +SIOUXisinrange +SIOUXselstart +SearchFolderForDNRP +SetDInfo +SetDefault +SetForeignPrivs +SetHasCustomIcon +SetIsInvisible +SetIsStationery +SetNameLocked +Share +StrToAddr +TclAllocateFreeObjects +TclChdir +TclCleanupByteCode +TclCleanupCommand +TclCompileBreakCmd +TclCompileCatchCmd +TclCompileContinueCmd +TclCompileDollarVar +TclCompileExpr +TclCompileExprCmd +TclCompileForCmd +TclCompileForeachCmd +TclCompileIfCmd +TclCompileIncrCmd +TclCompileQuotes +TclCompileSetCmd +TclCompileString +TclCompileWhileCmd +TclCopyAndCollapse +TclCopyChannel +TclCreateAuxData +TclCreateExecEnv +TclDate_TclDates +TclDate_TclDatev +TclDateact +TclDatechar +TclDatechk +TclDatedebug +TclDatedef +TclDateerrflag +TclDateexca +TclDatelval +TclDatenerrs +TclDatepact +TclDateparse +TclDatepgo +TclDateps +TclDatepv +TclDater1 +TclDater2 +TclDates +TclDatestate +TclDatetmp +TclDatev +TclDateval +TclDeleteCompiledLocalVars +TclDeleteExecEnv +TclDeleteVars +TclDoGlob +TclEmitForwardJump +TclExecuteByteCode +TclExpandCodeArray +TclExpandJumpFixupArray +TclExpandParseValue +TclExprFloatError +TclFileAttrsCmd +TclFileCopyCmd +TclFileDeleteCmd +TclFileMakeDirsCmd +TclFileRenameCmd +TclFindElement +TclFindProc +TclFixupForwardJump +TclFormatInt +TclFreeCompileEnv +TclFreeJumpFixupArray +TclFreeObj +TclFreePackageInfo +TclGetCwd +TclGetDate +TclGetDefaultStdChannel +TclGetElementOfIndexedArray +TclGetEnv +TclGetExceptionRangeForPc +TclGetExtension +TclGetFrame +TclGetIndexedScalar +TclGetIntForIndex +TclGetLoadedPackages +TclGetLong +TclGetNamespaceForQualName +TclGetOpenMode +TclGetOriginalCommand +TclGetRegError +TclGetSrcInfoForPc +TclGetUserHome +TclGlobalInvoke +TclGuessPackageName +TclHasSockets +TclHideUnsafeCommands +TclInExit +TclIncrElementOfIndexedArray +TclIncrIndexedScalar +TclIncrVar2 +TclInitByteCodeObj +TclInitCompileEnv +TclInitJumpFixupArray +TclInitNamespaces +TclInterpInit +TclInvoke +TclInvokeObjectCommand +TclInvokeStringCommand +TclIsProc +TclLoadFile +TclLooksLikeInt +TclLookupVar +TclMacAccess +TclMacCreateEnv +TclMacExitHandler +TclMacFOpenHack +TclMacInitExitToShell +TclMacInstallExitToShellPatch +TclMacOSErrorToPosixError +TclMacReadlink +TclMacRemoveTimer +TclMacStartTimer +TclMacStat +TclMacTimerExpired +TclMatchFiles +TclNeedSpace +TclObjIndexForString +TclObjInterpProc +TclObjInvoke +TclObjInvokeGlobal +TclParseBraces +TclParseNestedCmd +TclParseQuotes +TclPlatformExit +TclPlatformInit +TclPreventAliasLoop +TclPrintByteCodeObj +TclPrintInstruction +TclPrintSource +TclRegComp +TclRegError +TclRegExec +TclRenameCommand +TclResetShadowedCmdRefs +TclServiceIdle +TclSetElementOfIndexedArray +TclSetEnv +TclSetIndexedScalar +TclSetupEnv +TclSockGetPort +TclTeardownNamespace +TclTestChannelCmd +TclTestChannelEventCmd +TclUnsetEnv +TclUpdateReturnInfo +TclWordEnd +Tcl_AddErrorInfo +Tcl_AddObjErrorInfo +Tcl_AfterCmd +Tcl_Alloc +Tcl_AllowExceptions +Tcl_AppendAllObjTypes +Tcl_AppendElement +Tcl_AppendObjCmd +Tcl_AppendResult +Tcl_AppendStringsToObj +Tcl_AppendToObj +Tcl_ArrayObjCmd +Tcl_AsyncCreate +Tcl_AsyncDelete +Tcl_AsyncInvoke +Tcl_AsyncMark +Tcl_AsyncReady +Tcl_BackgroundError +Tcl_Backslash +Tcl_BeepObjCmd +Tcl_BinaryObjCmd +Tcl_BreakCmd +Tcl_CallWhenDeleted +Tcl_CancelIdleCall +Tcl_CaseObjCmd +Tcl_CatchObjCmd +Tcl_ClockObjCmd +Tcl_Close +Tcl_CommandComplete +Tcl_Concat +Tcl_ConcatObj +Tcl_ConcatObjCmd +Tcl_ContinueCmd +Tcl_ConvertCountedElement +Tcl_ConvertElement +Tcl_ConvertToType +Tcl_CreateAlias +Tcl_CreateAliasObj +Tcl_CreateChannel +Tcl_CreateChannelHandler +Tcl_CreateCloseHandler +Tcl_CreateCommand +Tcl_CreateEventSource +Tcl_CreateExitHandler +Tcl_CreateInterp +Tcl_CreateMathFunc +Tcl_CreateNamespace +Tcl_CreateObjCommand +Tcl_CreateSlave +Tcl_CreateTimerHandler +Tcl_CreateTrace +Tcl_DStringAppend +Tcl_DStringAppendElement +Tcl_DStringEndSublist +Tcl_DStringFree +Tcl_DStringGetResult +Tcl_DStringInit +Tcl_DStringResult +Tcl_DStringSetLength +Tcl_DStringStartSublist +Tcl_DbCkalloc +Tcl_DbCkfree +Tcl_DbCkrealloc +Tcl_DbDecrRefCount +Tcl_DbIsShared +Tcl_DbIncrRefCount +Tcl_DbNewBooleanObj +Tcl_DbNewDoubleObj +Tcl_DbNewListObj +Tcl_DbNewLongObj +Tcl_DbNewObj +Tcl_DbNewStringObj +Tcl_DeleteAssocData +Tcl_DeleteChannelHandler +Tcl_DeleteCloseHandler +Tcl_DeleteCommand +Tcl_DeleteCommandFromToken +Tcl_DeleteEventSource +Tcl_DeleteEvents +Tcl_DeleteExitHandler +Tcl_DeleteHashEntry +Tcl_DeleteHashTable +Tcl_DeleteInterp +Tcl_DeleteNamespace +Tcl_DeleteTimerHandler +Tcl_DeleteTrace +Tcl_DoOneEvent +Tcl_DoWhenIdle +Tcl_DontCallWhenDeleted +Tcl_DumpActiveMemory +Tcl_DuplicateObj +Tcl_EchoCmd +Tcl_Eof +Tcl_ErrnoId +Tcl_ErrnoMsg +Tcl_ErrorObjCmd +Tcl_Eval +Tcl_EvalFile +Tcl_EvalObj +Tcl_EvalObjCmd +Tcl_EventuallyFree +Tcl_ExecCmd +Tcl_Exit +Tcl_ExitObjCmd +Tcl_ExposeCommand +Tcl_ExprBoolean +Tcl_ExprBooleanObj +Tcl_ExprDouble +Tcl_ExprDoubleObj +Tcl_ExprLong +Tcl_ExprLongObj +Tcl_ExprObjCmd +Tcl_ExprString +Tcl_FconfigureCmd +Tcl_FcopyObjCmd +Tcl_FileEventCmd +Tcl_FileObjCmd +Tcl_Finalize +Tcl_FindCommand +Tcl_FindExecutable +Tcl_FindNamespace +Tcl_FindNamespaceVar +Tcl_FirstHashEntry +Tcl_Flush +Tcl_FlushObjCmd +Tcl_ForCmd +Tcl_ForeachObjCmd +Tcl_ForgetImport +Tcl_FormatCmd +Tcl_Free +Tcl_FreeResult +Tcl_GetAlias +Tcl_GetAliasObj +Tcl_GetAssocData +Tcl_GetBoolean +Tcl_GetBooleanFromObj +Tcl_GetChannel +Tcl_GetChannelBufferSize +Tcl_GetChannelHandle +Tcl_GetChannelInstanceData +Tcl_GetChannelMode +Tcl_GetChannelName +Tcl_GetChannelOption +Tcl_GetChannelType +Tcl_GetCommandFromObj +Tcl_GetCommandFullName +Tcl_GetCommandInfo +Tcl_GetCommandName +Tcl_GetCurrentNamespace +Tcl_GetDouble +Tcl_GetDoubleFromObj +Tcl_GetErrno +Tcl_GetGlobalNamespace +Tcl_GetHostName +Tcl_GetIndexFromObj +Tcl_GetInt +Tcl_GetIntFromObj +Tcl_GetInterpPath +Tcl_GetLongFromObj +Tcl_GetMaster +Tcl_GetOSTypeFromObj +Tcl_GetObjResult +Tcl_GetObjType +Tcl_GetPathType +Tcl_GetServiceMode +Tcl_GetSlave +Tcl_GetStdChannel +Tcl_GetStringFromObj +Tcl_GetStringResult +Tcl_GetVar +Tcl_GetVar2 +Tcl_GetVariableFullName +Tcl_Gets +Tcl_GetsObj +Tcl_GetsObjCmd +Tcl_GlobCmd +Tcl_GlobalEval +Tcl_GlobalEvalObj +Tcl_GlobalObjCmd +Tcl_HashStats +Tcl_HideCommand +Tcl_HistoryCmd +Tcl_IfCmd +Tcl_Import +Tcl_IncrCmd +Tcl_InfoObjCmd +Tcl_Init +Tcl_InitHashTable +Tcl_InitMemory +Tcl_InputBlocked +Tcl_InputBuffered +Tcl_InterpDeleted +Tcl_InterpObjCmd +Tcl_IsSafe +Tcl_JoinObjCmd +Tcl_JoinPath +Tcl_LappendObjCmd +Tcl_LindexObjCmd +Tcl_LinkVar +Tcl_LinsertObjCmd +Tcl_ListObjAppendElement +Tcl_ListObjAppendList +Tcl_ListObjCmd +Tcl_ListObjGetElements +Tcl_ListObjIndex +Tcl_ListObjLength +Tcl_ListObjReplace +Tcl_LlengthObjCmd +Tcl_LoadCmd +Tcl_LrangeObjCmd +Tcl_LreplaceObjCmd +Tcl_LsCmd +Tcl_LsearchObjCmd +Tcl_LsortObjCmd +Tcl_MacConvertTextResource +Tcl_MacEvalResource +Tcl_MacFindResource +Tcl_MacSetEventProc +Tcl_MacSourceObjCmd +Tcl_Main +Tcl_MakeSafe +Tcl_MakeTcpClientChannel +Tcl_Merge +Tcl_NamespaceObjCmd +Tcl_NewBooleanObj +Tcl_NewDoubleObj +Tcl_NewIntObj +Tcl_NewListObj +Tcl_NewLongObj +Tcl_NewOSTypeObj +Tcl_NewObj +Tcl_NewStringObj +Tcl_NextHashEntry +Tcl_NotifyChannel +Tcl_ObjGetVar2 +Tcl_ObjSetVar2 +Tcl_OpenCmd +Tcl_OpenFileChannel +Tcl_OpenTcpClient +Tcl_OpenTcpServer +Tcl_PackageCmd +Tcl_ParseVar +Tcl_PidObjCmd +Tcl_PkgProvide +Tcl_PkgRequire +Tcl_PopCallFrame +Tcl_PosixError +Tcl_Preserve +Tcl_PrintDouble +Tcl_ProcObjCmd +Tcl_PushCallFrame +Tcl_PutEnv +Tcl_PutsObjCmd +Tcl_PwdCmd +Tcl_QueueEvent +Tcl_Read +Tcl_ReadObjCmd +Tcl_Realloc +Tcl_RecordAndEval +Tcl_RegExpCompile +Tcl_RegExpExec +Tcl_RegExpMatch +Tcl_RegExpRange +Tcl_RegexpCmd +Tcl_RegisterChannel +Tcl_RegisterObjType +Tcl_RegsubCmd +Tcl_Release +Tcl_RenameObjCmd +Tcl_ResetResult +Tcl_ResourceObjCmd +Tcl_ReturnObjCmd +Tcl_ScanCmd +Tcl_ScanCountedElement +Tcl_ScanElement +Tcl_Seek +Tcl_SeekCmd +Tcl_ServiceAll +Tcl_ServiceEvent +Tcl_SetAssocData +Tcl_SetBooleanObj +Tcl_SetChannelBufferSize +Tcl_SetChannelOption +Tcl_SetCmd +Tcl_SetCommandInfo +Tcl_SetDoubleObj +Tcl_SetErrno +Tcl_SetErrorCode +Tcl_SetIntObj +Tcl_SetListObj +Tcl_SetLongObj +Tcl_SetMaxBlockTime +Tcl_SetOSTypeObj +Tcl_SetObjErrorCode +Tcl_SetObjLength +Tcl_SetObjResult +Tcl_SetPanicProc +Tcl_SetRecursionLimit +Tcl_SetResult +Tcl_SetServiceMode +Tcl_SetStdChannel +Tcl_SetStringObj +Tcl_SetTimer +Tcl_SetVar +Tcl_SetVar2 +Tcl_SignalId +Tcl_SignalMsg +Tcl_Sleep +Tcl_SocketCmd +Tcl_SourceObjCmd +Tcl_SourceRCFile +Tcl_SplitList +Tcl_SplitPath +Tcl_StaticPackage +Tcl_StringMatch +Tcl_StringObjCmd +Tcl_SubstCmd +Tcl_SwitchObjCmd +Tcl_Tell +Tcl_TellCmd +Tcl_TimeObjCmd +Tcl_TraceCmd +Tcl_TraceVar +Tcl_TraceVar2 +Tcl_TranslateFileName +Tcl_Ungets +Tcl_UnlinkVar +Tcl_UnregisterChannel +Tcl_UnsetObjCmd +Tcl_UnsetVar +Tcl_UnsetVar2 +Tcl_UntraceVar +Tcl_UntraceVar2 +Tcl_UpVar +Tcl_UpVar2 +Tcl_UpdateCmd +Tcl_UpdateLinkedVar +Tcl_UplevelObjCmd +Tcl_UpvarObjCmd +Tcl_ValidateAllMemory +Tcl_VarEval +Tcl_VarTraceInfo +Tcl_VarTraceInfo2 +Tcl_VariableObjCmd +Tcl_VwaitCmd +Tcl_WaitForEvent +Tcl_WaitPid +Tcl_WhileCmd +Tcl_Write +Tcl_WrongNumArgs +TclpAlloc +TclpCopyDirectory +TclpCopyFile +TclpCreateDirectory +TclpDeleteFile +TclpFree +TclpGetClicks +TclpGetDate +TclpGetSeconds +TclpGetTime +TclpGetTimeZone +TclpListVolumes +TclpRealloc +TclpRemoveDirectory +TclpRenameFile +TrapExists +TruncPString +UnlockRange +UnmountAndEject +Unshare +VolumeMount +WriteCharsToConsole +XGetVInfo +_Ctype +_Stderr +_Stoul +abort +abs +acosf +appMemory +asctime +asinf +atan +atan2 +atan2_d_dd +atan2_d_pdpd +atan2_r_prpr +atan2_r_rr +atan2f +atan_d_d +atan_d_pd +atan_r_pr +atan_r_r +atanf +atexit +atof +atoi +atol +bsearch +builtinFuncTable +calloc +ccommand +ceilf +chdir +clearerr +clock +close +closeUPP +completeUPP +cos +cos_d_d +cos_d_pd +cos_r_pr +cos_r_r +cosf +coshf +creat +ctime +cuserid +difftime +div +environ +errno +exec +exit +exp +exp_d_d +exp_d_pd +exp_r_pr +exp_r_r +expf +fabsf +fclose +fcntl +fdopen +feof +ferror +fflush +fgetc +fgetpos +fgets +fileno +floorf +fmodf +fopen +fprintf +fputc +fputs +fread +free +freopen +frexpf +fscanf +fseek +fsetpos +fstat +ftell +fwrite +getStdChannelsProc +getc +getchar +getcwd +getenv +getlogin +gets +gmtime +instructionTable +isalnum +isalpha +isatty +iscntrl +isdigit +isgraph +islower +isprint +ispunct +isspace +isupper +isxdigit +labs +ldexpf +ldiv +localeconv +localtime +log +log10 +log10_d_d +log10_d_pd +log10f +log_d_d +log_d_pd +logf +longjmp +lseek +malloc +mblen +mbstowcs +mbtowc +memchr +memcmp +memcpy +memmove +memset +mkdir +mktime +open +panic +panicProc +perror +pow +power_d_dd +powf +printf +putc +putchar +puts +qsort +raise +rand +read +realloc +remove +rename +resultUPP +rewind +rmdir +scanf +setbuf +setlocale +setvbuf +signal +sin +sin_d_d +sin_d_pd +sin_r_pr +sin_r_r +sinf +sinhf +sleep +sprintf +sqrt +sqrt_d_d +sqrt_d_pd +sqrt_r_pr +sqrt_r_r +sqrtf +srand +sscanf +stat +strcasecmp +strcat +strchr +strcmp +strcoll +strcpy +strcspn +strerror +strftime +strlen +strncasecmp +strncat +strncmp +strncpy +strpbrk +strrchr +strspn +strstr +strtod +strtok +strtol +strtoul +strxfrm +system +systemMemory +tanf +tanhf +tclBooleanType +tclByteCodeType +tclCmdNameType +tclDoubleType +tclDummyLinkVarPtr +tclExecutableName +tclFreeObjList +tclIndexType +tclIntType +tclListType +tclMemDumpFileName +tclNsNameType +tclPlatform +tclStringType +tclTraceCompile +tclTraceExec +tclTypeTable +tcl_MathInProgress +tclpFileAttrProcs +tclpFileAttrStrings +tell +time +tmpfile +tmpnam +tolower +toupper +ttyname +uname +ungetc +unlink +utime +utimes +vfprintf +vprintf +vsprintf +wcstombs +wctob +wctomb +write +#DTGetAPPL +#DTGetComment +#FSpDTGetAPPL +#FSpDTGetComment +#TclMacInitializeFragment +#TclMacTerminateFragment +#_Aldata +#_Assert +#_Atcount +#_Atfuns +#_Clocale +#_Closreg +#_Costate +#_Daysto +#_Dbl +#_Defloc +#_Environ +#_Environ1 +#_Fgpos +#_Files +#_Flt +#_Fopen +#_Foprep +#_Fread +#_Freeloc +#_Frprep +#_Fspos +#_Fwprep +#_Fwrite +#_Genld +#_Gentime +#_Getdst +#_Getfld +#_Getfloat +#_Getint +#_Getloc +#_Getmem +#_Getstr +#_Gettime +#_Getzone +#_Isdst +#_Ldbl +#_Ldtob +#_Litob +#_Locale +#_Locsum +#_Loctab +#_Locterm +#_Locvar +#_MWERKS_Atcount +#_MWERKS_Atfuns +#_Makeloc +#_Makestab +#_Makewct +#_Mbcurmax +#_Mbstate +#_Mbtowc +#_Nnl +#_PJP_C_Copyright +#_Printf +#_Putfld +#_Putstr +#_Puttxt +#_Randseed +#_Readloc +#_Scanf +#_Setloc +#_Skip +#_Stdin +#_Stdout +#_Stod +#_Stof +#_Stoflt +#_Stold +#_Strerror +#_Strftime +#_Strxfrm +#_Times +#_Tolower +#_Toupper +#_Ttotm +#_WCostate +#_Wcstate +#_Wctob +#_Wctomb +#_Wctrans +#_Wctype +#__CheckForSystem7 +#__RemoveConsoleHandler__ +#__aborting +#__ctopstring +#__cvt_fp2unsigned +#__getcreator +#__gettype +#__initialize +#__myraise +#__ptmf_null +#__ptr_glue +#__system7present +#__terminate +#__ttyname +#_atexit +#_exit +#_fcreator +#_ftype diff --git a/tcl/mac/tclMacSock.c b/tcl/mac/tclMacSock.c new file mode 100644 index 00000000000..690bee29699 --- /dev/null +++ b/tcl/mac/tclMacSock.c @@ -0,0 +1,2615 @@ +/* + * tclMacSock.c + * + * Channel drivers for Macintosh sockets. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tclInt.h" +#include "tclPort.h" +#include "tclMacInt.h" +#include <AddressXlation.h> +#include <Aliases.h> +#undef Status +#include <Devices.h> +#include <Errors.h> +#include <Events.h> +#include <Files.h> +#include <Gestalt.h> +#include <MacTCP.h> +#include <Processes.h> +#include <Strings.h> + +/* + * The following variable is used to tell whether this module has been + * initialized. + */ + +static int initialized = 0; + +/* + * If debugging is on we may drop into the debugger to handle certain cases + * that are not supposed to happen. Otherwise, we change ignore the error + * and most code should handle such errors ok. + */ + +#ifndef TCL_DEBUG + #define Debugger() +#endif + +/* + * The preferred buffer size for Macintosh channels. + */ + +#define CHANNEL_BUF_SIZE 8192 + +/* + * Port information structure. Used to match service names + * to a Tcp/Ip port number. + */ + +typedef struct { + char *name; /* Name of service. */ + int port; /* Port number. */ +} PortInfo; + +/* + * This structure describes per-instance state of a tcp based channel. + */ + +typedef struct TcpState { + TCPiopb pb; /* Parameter block used by this stream. + * This must be in the first position. */ + ProcessSerialNumber psn; /* PSN used to wake up process. */ + StreamPtr tcpStream; /* Macintosh tcp stream pointer. */ + int port; /* The port we are connected to. */ + int flags; /* Bit field comprised of the flags + * described below. */ + int checkMask; /* OR'ed combination of TCL_READABLE and + * TCL_WRITABLE as set by an asynchronous + * event handler. */ + int watchMask; /* OR'ed combination of TCL_READABLE and + * TCL_WRITABLE as set by Tcl_WatchFile. */ + Tcl_TcpAcceptProc *acceptProc; /* Proc to call on accept. */ + ClientData acceptProcData; /* The data for the accept proc. */ + wdsEntry dataSegment[2]; /* List of buffers to be written async. */ + rdsEntry rdsarray[5+1]; /* Array used when cleaning out recieve + * buffers on a closing socket. */ + Tcl_Channel channel; /* Channel associated with this socket. */ + struct TcpState *nextPtr; /* The next socket on the global socket + * list. */ +} TcpState; + +/* + * This structure is used by domain name resolver callback. + */ + +typedef struct DNRState { + struct hostInfo hostInfo; /* Data structure used by DNR functions. */ + int done; /* Flag to determine when we are done. */ + ProcessSerialNumber psn; /* Process to wake up when we are done. */ +} DNRState; + +/* + * The following macros may be used to set the flags field of + * a TcpState structure. + */ + +#define TCP_ASYNC_SOCKET (1<<0) /* The socket is in async mode. */ +#define TCP_ASYNC_CONNECT (1<<1) /* The socket is trying to connect. */ +#define TCP_CONNECTED (1<<2) /* The socket is connected. */ +#define TCP_PENDING (1<<3) /* A SocketEvent is on the queue. */ +#define TCP_LISTENING (1<<4) /* This socket is listening for + * a connection. */ +#define TCP_LISTEN_CONNECT (1<<5) /* Someone has connect to the + * listening port. */ +#define TCP_REMOTE_CLOSED (1<<6) /* The remote side has closed + * the connection. */ +#define TCP_RELEASE (1<<7) /* The socket may now be released. */ +#define TCP_WRITING (1<<8) /* A background write is in progress. */ +#define TCP_SERVER_ZOMBIE (1<<9) /* The server can no longer accept connects. */ + +/* + * The following structure is what is added to the Tcl event queue when + * a socket event occurs. + */ + +typedef struct SocketEvent { + Tcl_Event header; /* Information that is standard for + * all events. */ + TcpState *statePtr; /* Socket descriptor that is ready. */ + StreamPtr tcpStream; /* Low level Macintosh stream. */ +} SocketEvent; + +/* + * Static routines for this file: + */ + +static pascal void CleanUpExitProc _ANSI_ARGS_((void)); +static void ClearZombieSockets _ANSI_ARGS_((void)); +static void CloseCompletionRoutine _ANSI_ARGS_((TCPiopb *pb)); +static TcpState * CreateSocket _ANSI_ARGS_((Tcl_Interp *interp, + int port, char *host, char *myAddr, int myPort, + int server, int async)); +static pascal void DNRCompletionRoutine _ANSI_ARGS_(( + struct hostInfo *hostinfoPtr, + DNRState *dnrStatePtr)); +static void FreeSocketInfo _ANSI_ARGS_((TcpState *statePtr)); +static long GetBufferSize _ANSI_ARGS_((void)); +static OSErr GetHostFromString _ANSI_ARGS_((char *name, + ip_addr *address)); +static OSErr GetLocalAddress _ANSI_ARGS_((unsigned long *addr)); +static void IOCompletionRoutine _ANSI_ARGS_((TCPiopb *pb)); +static void InitMacTCPParamBlock _ANSI_ARGS_((TCPiopb *pBlock, + int csCode)); +static void InitSockets _ANSI_ARGS_((void)); +static TcpState * NewSocketInfo _ANSI_ARGS_((StreamPtr stream)); +static OSErr ResolveAddress _ANSI_ARGS_((ip_addr tcpAddress, + Tcl_DString *dsPtr)); +static void SocketCheckProc _ANSI_ARGS_((ClientData clientData, + int flags)); +static int SocketEventProc _ANSI_ARGS_((Tcl_Event *evPtr, + int flags)); +static void SocketExitHandler _ANSI_ARGS_((ClientData clientData)); +static void SocketFreeProc _ANSI_ARGS_((ClientData clientData)); +static int SocketReady _ANSI_ARGS_((TcpState *statePtr)); +static void SocketSetupProc _ANSI_ARGS_((ClientData clientData, + int flags)); +static void TcpAccept _ANSI_ARGS_((TcpState *statePtr)); +static int TcpBlockMode _ANSI_ARGS_((ClientData instanceData, int mode)); +static int TcpClose _ANSI_ARGS_((ClientData instanceData, + Tcl_Interp *interp)); +static int TcpGetHandle _ANSI_ARGS_((ClientData instanceData, + int direction, ClientData *handlePtr)); +static int TcpGetOptionProc _ANSI_ARGS_((ClientData instanceData, + Tcl_Interp *interp, char *optionName, + Tcl_DString *dsPtr)); +static int TcpInput _ANSI_ARGS_((ClientData instanceData, + char *buf, int toRead, int *errorCodePtr)); +static int TcpOutput _ANSI_ARGS_((ClientData instanceData, + char *buf, int toWrite, int *errorCodePtr)); +static void TcpWatch _ANSI_ARGS_((ClientData instanceData, + int mask)); +static int WaitForSocketEvent _ANSI_ARGS_((TcpState *infoPtr, + int mask, int *errorCodePtr)); + +/* + * This structure describes the channel type structure for TCP socket + * based IO: + */ + +static Tcl_ChannelType tcpChannelType = { + "tcp", /* Type name. */ + TcpBlockMode, /* Set blocking or + * non-blocking mode.*/ + TcpClose, /* Close proc. */ + TcpInput, /* Input proc. */ + TcpOutput, /* Output proc. */ + NULL, /* Seek proc. */ + NULL, /* Set option proc. */ + TcpGetOptionProc, /* Get option proc. */ + TcpWatch, /* Initialize notifier. */ + TcpGetHandle /* Get handles out of channel. */ +}; + +/* + * Universal Procedure Pointers (UPP) for various callback + * routines used by MacTcp code. + */ + +ResultUPP resultUPP = NULL; +TCPIOCompletionUPP completeUPP = NULL; +TCPIOCompletionUPP closeUPP = NULL; + +/* + * Built-in commands, and the procedures associated with them: + */ + +static PortInfo portServices[] = { + {"echo", 7}, + {"discard", 9}, + {"systat", 11}, + {"daytime", 13}, + {"netstat", 15}, + {"chargen", 19}, + {"ftp-data", 20}, + {"ftp", 21}, + {"telnet", 23}, + {"telneto", 24}, + {"smtp", 25}, + {"time", 37}, + {"whois", 43}, + {"domain", 53}, + {"gopher", 70}, + {"finger", 79}, + {"hostnames", 101}, + {"sunrpc", 111}, + {"nntp", 119}, + {"exec", 512}, + {"login", 513}, + {"shell", 514}, + {"printer", 515}, + {"courier", 530}, + {"uucp", 540}, + {NULL, 0}, +}; + +/* + * Every open socket has an entry on the following list. + */ + +static TcpState *socketList = NULL; + +/* + * Globals for holding information about OS support for sockets. + */ + +static int socketsTestInited = false; +static int hasSockets = false; +static short driverRefNum = 0; +static int socketNumber = 0; +static int socketBufferSize = CHANNEL_BUF_SIZE; +static ProcessSerialNumber applicationPSN; + +/* + *---------------------------------------------------------------------- + * + * InitSockets -- + * + * Load the MacTCP driver and open the name resolver. We also + * create several UPP's used by our code. Lastly, we install + * a patch to ExitToShell to clean up socket connections if + * we are about to exit. + * + * Results: + * 1 if successful, 0 on failure. + * + * Side effects: + * Creates a new event source, loads the MacTCP driver, + * registers an exit to shell callback. + * + *---------------------------------------------------------------------- + */ + +#define gestaltMacTCPVersion 'mtcp' +static void +InitSockets() +{ + ParamBlockRec pb; + OSErr err; + long response; + + initialized = 1; + Tcl_CreateExitHandler(SocketExitHandler, (ClientData) NULL); + + if (Gestalt(gestaltMacTCPVersion, &response) == noErr) { + hasSockets = true; + } else { + hasSockets = false; + } + + if (!hasSockets) { + return; + } + + /* + * Load MacTcp driver and name server resolver. + */ + + + pb.ioParam.ioCompletion = 0L; + pb.ioParam.ioNamePtr = "\p.IPP"; + pb.ioParam.ioPermssn = fsCurPerm; + err = PBOpenSync(&pb); + if (err != noErr) { + hasSockets = 0; + return; + } + driverRefNum = pb.ioParam.ioRefNum; + + socketBufferSize = GetBufferSize(); + err = OpenResolver(NULL); + if (err != noErr) { + hasSockets = 0; + return; + } + + GetCurrentProcess(&applicationPSN); + /* + * Create UPP's for various callback routines. + */ + + resultUPP = NewResultProc(DNRCompletionRoutine); + completeUPP = NewTCPIOCompletionProc(IOCompletionRoutine); + closeUPP = NewTCPIOCompletionProc(CloseCompletionRoutine); + + /* + * Install an ExitToShell patch. We use this patch instead + * of the Tcl exit mechanism because we need to ensure that + * these routines are cleaned up even if we crash or are forced + * to quit. There are some circumstances when the Tcl exit + * handlers may not fire. + */ + + TclMacInstallExitToShellPatch(CleanUpExitProc); + + Tcl_CreateEventSource(SocketSetupProc, SocketCheckProc, NULL); + + initialized = 1; +} + +/* + *---------------------------------------------------------------------- + * + * SocketExitHandler -- + * + * Callback invoked during exit clean up to deinitialize the + * socket module. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +SocketExitHandler( + ClientData clientData) /* Not used. */ +{ + if (hasSockets) { + Tcl_DeleteEventSource(SocketSetupProc, SocketCheckProc, NULL); + /* CleanUpExitProc(); + TclMacDeleteExitToShellPatch(CleanUpExitProc); */ + } + initialized = 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclHasSockets -- + * + * This function determines whether sockets are available on the + * current system and returns an error in interp if they are not. + * Note that interp may be NULL. + * + * Results: + * Returns TCL_OK if the system supports sockets, or TCL_ERROR with + * an error in interp. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclHasSockets( + Tcl_Interp *interp) /* Interp for error messages. */ +{ + if (!initialized) { + InitSockets(); + } + + if (hasSockets) { + return TCL_OK; + } + if (interp != NULL) { + Tcl_AppendResult(interp, "sockets are not available on this system", + NULL); + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * SocketSetupProc -- + * + * This procedure is invoked before Tcl_DoOneEvent blocks waiting + * for an event. + * + * Results: + * None. + * + * Side effects: + * Adjusts the block time if needed. + * + *---------------------------------------------------------------------- + */ + +static void +SocketSetupProc( + ClientData data, /* Not used. */ + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + TcpState *statePtr; + Tcl_Time blockTime = { 0, 0 }; + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + /* + * Check to see if there is a ready socket. If so, poll. + */ + + for (statePtr = socketList; statePtr != NULL; + statePtr = statePtr->nextPtr) { + if (statePtr->flags & TCP_RELEASE) { + continue; + } + if (SocketReady(statePtr)) { + Tcl_SetMaxBlockTime(&blockTime); + break; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * SocketCheckProc -- + * + * This procedure is called by Tcl_DoOneEvent to check the socket + * event source for events. + * + * Results: + * None. + * + * Side effects: + * May queue an event. + * + *---------------------------------------------------------------------- + */ + +static void +SocketCheckProc( + ClientData data, /* Not used. */ + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + TcpState *statePtr; + SocketEvent *evPtr; + TcpState dummyState; + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + /* + * Queue events for any ready sockets that don't already have events + * queued (caused by persistent states that won't generate WinSock + * events). + */ + + for (statePtr = socketList; statePtr != NULL; + statePtr = statePtr->nextPtr) { + /* + * Check to see if this socket is dead and needs to be cleaned + * up. We use a dummy statePtr whose only valid field is the + * nextPtr to allow the loop to continue even if the element + * is deleted. + */ + + if (statePtr->flags & TCP_RELEASE) { + if (!(statePtr->flags & TCP_PENDING)) { + dummyState.nextPtr = statePtr->nextPtr; + SocketFreeProc(statePtr); + statePtr = &dummyState; + } + continue; + } + + if (!(statePtr->flags & TCP_PENDING) && SocketReady(statePtr)) { + statePtr->flags |= TCP_PENDING; + evPtr = (SocketEvent *) ckalloc(sizeof(SocketEvent)); + evPtr->header.proc = SocketEventProc; + evPtr->statePtr = statePtr; + evPtr->tcpStream = statePtr->tcpStream; + Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * SocketReady -- + * + * This function checks the current state of a socket to see + * if any interesting conditions are present. + * + * Results: + * Returns 1 if an event that someone is watching is present, else + * returns 0. + * + * Side effects: + * Updates the checkMask for the socket to reflect any newly + * detected events. + * + *---------------------------------------------------------------------- + */ + +static int +SocketReady( + TcpState *statePtr) +{ + TCPiopb statusPB; + int foundSomething = 0; + int didStatus = 0; + int amount; + OSErr err; + + if (statePtr->flags & TCP_LISTEN_CONNECT) { + foundSomething = 1; + statePtr->checkMask |= TCL_READABLE; + } + if (statePtr->watchMask & TCL_READABLE) { + if (statePtr->checkMask & TCL_READABLE) { + foundSomething = 1; + } else if (statePtr->flags & TCP_CONNECTED) { + statusPB.ioCRefNum = driverRefNum; + statusPB.tcpStream = statePtr->tcpStream; + statusPB.csCode = TCPStatus; + err = PBControlSync((ParmBlkPtr) &statusPB); + didStatus = 1; + + /* + * We make the fchannel readable if 1) we get an error, + * 2) there is more data available, or 3) we detect + * that a close from the remote connection has arrived. + */ + + if ((err != noErr) || + (statusPB.csParam.status.amtUnreadData > 0) || + (statusPB.csParam.status.connectionState == 14)) { + statePtr->checkMask |= TCL_READABLE; + foundSomething = 1; + } + } + } + if (statePtr->watchMask & TCL_WRITABLE) { + if (statePtr->checkMask & TCL_WRITABLE) { + foundSomething = 1; + } else if (statePtr->flags & TCP_CONNECTED) { + if (!didStatus) { + statusPB.ioCRefNum = driverRefNum; + statusPB.tcpStream = statePtr->tcpStream; + statusPB.csCode = TCPStatus; + err = PBControlSync((ParmBlkPtr) &statusPB); + } + + /* + * If there is an error or there if there is room to + * send more data we make the channel writeable. + */ + + amount = statusPB.csParam.status.sendWindow - + statusPB.csParam.status.amtUnackedData; + if ((err != noErr) || (amount > 0)) { + statePtr->checkMask |= TCL_WRITABLE; + foundSomething = 1; + } + } + } + return foundSomething; +} + +/* + *---------------------------------------------------------------------- + * + * InitMacTCPParamBlock-- + * + * Initialize a MacTCP parameter block. + * + * Results: + * None. + * + * Side effects: + * Initializes the parameter block. + * + *---------------------------------------------------------------------- + */ + +static void +InitMacTCPParamBlock( + TCPiopb *pBlock, /* Tcp parmeter block. */ + int csCode) /* Tcp operation code. */ +{ + memset(pBlock, 0, sizeof(TCPiopb)); + pBlock->ioResult = 1; + pBlock->ioCRefNum = driverRefNum; + pBlock->csCode = (short) csCode; +} + +/* + *---------------------------------------------------------------------- + * + * TcpBlockMode -- + * + * Set blocking or non-blocking mode on channel. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or non-blocking mode. + * + *---------------------------------------------------------------------- + */ + +static int +TcpBlockMode( + ClientData instanceData, /* Channel state. */ + int mode) /* The mode to set. */ +{ + TcpState *statePtr = (TcpState *) instanceData; + + if (mode == TCL_MODE_BLOCKING) { + statePtr->flags &= ~TCP_ASYNC_SOCKET; + } else { + statePtr->flags |= TCP_ASYNC_SOCKET; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TcpClose -- + * + * Close the socket. + * + * Results: + * 0 if successful, the value of errno if failed. + * + * Side effects: + * Closes the socket. + * + *---------------------------------------------------------------------- + */ + +static int +TcpClose( + ClientData instanceData, /* The socket to close. */ + Tcl_Interp *interp) /* Interp for error messages. */ +{ + TcpState *statePtr = (TcpState *) instanceData; + StreamPtr tcpStream; + TCPiopb closePB; + OSErr err; + + tcpStream = statePtr->tcpStream; + statePtr->flags &= ~TCP_CONNECTED; + + /* + * If this is a server socket we can't use the statePtr + * param block because it is in use. However, we can + * close syncronously. + */ + + if ((statePtr->flags & TCP_LISTENING) || + (statePtr->flags & TCP_LISTEN_CONNECT)) { + InitMacTCPParamBlock(&closePB, TCPClose); + closePB.tcpStream = tcpStream; + closePB.ioCompletion = NULL; + err = PBControlSync((ParmBlkPtr) &closePB); + if (err != noErr) { + Debugger(); + panic("error closing server socket"); + } + statePtr->flags |= TCP_RELEASE; + + /* + * Server sockets are closed sync. Therefor, we know it is OK to + * release the socket now. + */ + + InitMacTCPParamBlock(&statePtr->pb, TCPRelease); + statePtr->pb.tcpStream = statePtr->tcpStream; + err = PBControlSync((ParmBlkPtr) &statePtr->pb); + if (err != noErr) { + panic("error releasing server socket"); + } + + /* + * Free the buffer space used by the socket and the + * actual socket state data structure. + */ + + ckfree((char *) statePtr->pb.csParam.create.rcvBuff); + FreeSocketInfo(statePtr); + return 0; + } + + /* + * If this socket is in the midddle on async connect we can just + * abort the connect and release the stream right now. + */ + + if (statePtr->flags & TCP_ASYNC_CONNECT) { + InitMacTCPParamBlock(&closePB, TCPClose); + closePB.tcpStream = tcpStream; + closePB.ioCompletion = NULL; + err = PBControlSync((ParmBlkPtr) &closePB); + if (err != noErr) { + panic("error closing async connect socket"); + } + statePtr->flags |= TCP_RELEASE; + + InitMacTCPParamBlock(&statePtr->pb, TCPRelease); + statePtr->pb.tcpStream = statePtr->tcpStream; + err = PBControlSync((ParmBlkPtr) &statePtr->pb); + if (err != noErr) { + panic("error releasing async connect socket"); + } + + /* + * Free the buffer space used by the socket and the + * actual socket state data structure. + */ + + ckfree((char *) statePtr->pb.csParam.create.rcvBuff); + FreeSocketInfo(statePtr); + return 0; + } + + /* + * Client sockets: + * If a background write is in progress, don't close + * the socket yet. The completion routine for the + * write will take care of it. + */ + + if (!(statePtr->flags & TCP_WRITING)) { + InitMacTCPParamBlock(&statePtr->pb, TCPClose); + statePtr->pb.tcpStream = tcpStream; + statePtr->pb.ioCompletion = closeUPP; + statePtr->pb.csParam.close.userDataPtr = (Ptr) statePtr; + err = PBControlAsync((ParmBlkPtr) &statePtr->pb); + if (err != noErr) { + Debugger(); + statePtr->flags |= TCP_RELEASE; + /* return 0; */ + } + } + + SocketFreeProc(instanceData); + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * CloseCompletionRoutine -- + * + * Handles the close protocol for a Tcp socket. This will do + * a series of calls to release all data currently buffered for + * the socket. This is important to do to as it allows the remote + * connection to recieve and issue it's own close on the socket. + * Note that this function is running at interupt time and can't + * allocate memory or do much else except set state. + * + * Results: + * None. + * + * Side effects: + * The buffers for the socket are flushed. + * + *---------------------------------------------------------------------- + */ + +static void +CloseCompletionRoutine( + TCPiopb *pbPtr) /* Tcp parameter block. */ +{ + TcpState *statePtr; + OSErr err; + + if (pbPtr->csCode == TCPClose) { + statePtr = (TcpState *) (pbPtr->csParam.close.userDataPtr); + } else { + statePtr = (TcpState *) (pbPtr->csParam.receive.userDataPtr); + } + + /* + * It's very bad if the statePtr is nNULL - we should probably panic... + */ + + if (statePtr == NULL) { + Debugger(); + return; + } + + WakeUpProcess(&statePtr->psn); + + /* + * If there is an error we assume the remote side has already + * close. We are done closing as soon as we decide that the + * remote connection has closed. + */ + + if (pbPtr->ioResult != noErr) { + statePtr->flags |= TCP_RELEASE; + return; + } + if (statePtr->flags & TCP_REMOTE_CLOSED) { + statePtr->flags |= TCP_RELEASE; + return; + } + + /* + * If we just did a recieve we need to return the buffers. + * Otherwise, attempt to recieve more data until we recieve an + * error (usually because we have no more data). + */ + + if (statePtr->pb.csCode == TCPNoCopyRcv) { + InitMacTCPParamBlock(&statePtr->pb, TCPRcvBfrReturn); + statePtr->pb.tcpStream = statePtr->tcpStream; + statePtr->pb.ioCompletion = closeUPP; + statePtr->pb.csParam.receive.rdsPtr = (Ptr) statePtr->rdsarray; + statePtr->pb.csParam.receive.userDataPtr = (Ptr) statePtr; + err = PBControlAsync((ParmBlkPtr) &statePtr->pb); + } else { + InitMacTCPParamBlock(&statePtr->pb, TCPNoCopyRcv); + statePtr->pb.tcpStream = statePtr->tcpStream; + statePtr->pb.ioCompletion = closeUPP; + statePtr->pb.csParam.receive.commandTimeoutValue = 1; + statePtr->pb.csParam.receive.rdsPtr = (Ptr) statePtr->rdsarray; + statePtr->pb.csParam.receive.rdsLength = 5; + statePtr->pb.csParam.receive.userDataPtr = (Ptr) statePtr; + err = PBControlAsync((ParmBlkPtr) &statePtr->pb); + } + + if (err != noErr) { + statePtr->flags |= TCP_RELEASE; + } +} +/* + *---------------------------------------------------------------------- + * + * SocketFreeProc -- + * + * This callback is invoked in order to delete + * the notifier data associated with a file handle. + * + * Results: + * None. + * + * Side effects: + * Removes the SocketInfo from the global socket list. + * + *---------------------------------------------------------------------- + */ + +static void +SocketFreeProc( + ClientData clientData) /* Channel state. */ +{ + TcpState *statePtr = (TcpState *) clientData; + OSErr err; + TCPiopb statusPB; + + /* + * Get the status of this connection. We need to do a + * few tests to see if it's OK to release the stream now. + */ + + if (!(statePtr->flags & TCP_RELEASE)) { + return; + } + statusPB.ioCRefNum = driverRefNum; + statusPB.tcpStream = statePtr->tcpStream; + statusPB.csCode = TCPStatus; + err = PBControlSync((ParmBlkPtr) &statusPB); + if ((statusPB.csParam.status.connectionState == 0) || + (statusPB.csParam.status.connectionState == 2)) { + /* + * If the conection state is 0 then this was a client + * connection and it's closed. If it is 2 then this a + * server client and we may release it. If it isn't + * one of those values then we return and we'll try to + * clean up later. + */ + + } else { + return; + } + + /* + * The Close request is made async. We know it's + * OK to release the socket when the TCP_RELEASE flag + * gets set. + */ + + InitMacTCPParamBlock(&statePtr->pb, TCPRelease); + statePtr->pb.tcpStream = statePtr->tcpStream; + err = PBControlSync((ParmBlkPtr) &statePtr->pb); + if (err != noErr) { + Debugger(); /* Ignoreing leaves stranded stream. Is there an + alternative? */ + } + + /* + * Free the buffer space used by the socket and the + * actual socket state data structure. + */ + + ckfree((char *) statePtr->pb.csParam.create.rcvBuff); + FreeSocketInfo(statePtr); +} + +/* + *---------------------------------------------------------------------- + * + * TcpInput -- + * + * Reads input from the IO channel into the buffer given. Returns + * count of how many bytes were actually read, and an error + * indication. + * + * Results: + * A count of how many bytes were read is returned. A value of -1 + * implies an error occured. A value of zero means we have reached + * the end of data (EOF). + * + * Side effects: + * Reads input from the actual channel. + * + *---------------------------------------------------------------------- + */ + +int +TcpInput( + ClientData instanceData, /* Channel state. */ + char *buf, /* Where to store data read. */ + int bufSize, /* How much space is available + * in the buffer? */ + int *errorCodePtr) /* Where to store error code. */ +{ + TcpState *statePtr = (TcpState *) instanceData; + StreamPtr tcpStream; + OSErr err; + TCPiopb statusPB; + int toRead, dataAvail; + + *errorCodePtr = 0; + errno = 0; + tcpStream = statePtr->tcpStream; + + if (bufSize == 0) { + return 0; + } + toRead = bufSize; + + /* + * First check to see if EOF was already detected, to prevent + * calling the socket stack after the first time EOF is detected. + */ + + if (statePtr->flags & TCP_REMOTE_CLOSED) { + return 0; + } + + /* + * If an asynchronous connect is in progress, attempt to wait for it + * to complete before reading. + */ + + if ((statePtr->flags & TCP_ASYNC_CONNECT) + && ! WaitForSocketEvent(statePtr, TCL_READABLE, errorCodePtr)) { + return -1; + } + + /* + * No EOF, and it is connected, so try to read more from the socket. + * If the socket is blocking, we keep trying until there is data + * available or the socket is closed. + */ + + while (1) { + + statusPB.ioCRefNum = driverRefNum; + statusPB.tcpStream = tcpStream; + statusPB.csCode = TCPStatus; + err = PBControlSync((ParmBlkPtr) &statusPB); + if (err != noErr) { + Debugger(); + statePtr->flags |= TCP_REMOTE_CLOSED; + return 0; /* EOF */ + } + dataAvail = statusPB.csParam.status.amtUnreadData; + if (dataAvail < bufSize) { + toRead = dataAvail; + } else { + toRead = bufSize; + } + if (toRead != 0) { + /* + * Try to read the data. + */ + + InitMacTCPParamBlock(&statusPB, TCPRcv); + statusPB.tcpStream = tcpStream; + statusPB.csParam.receive.rcvBuff = buf; + statusPB.csParam.receive.rcvBuffLen = toRead; + err = PBControlSync((ParmBlkPtr) &statusPB); + + statePtr->checkMask &= ~TCL_READABLE; + switch (err) { + case noErr: + /* + * The channel remains readable only if this read succeds + * and we had more data then the size of the buffer we were + * trying to fill. Use the info from the call to status to + * determine this. + */ + + if (dataAvail > bufSize) { + statePtr->checkMask |= TCL_READABLE; + } + return statusPB.csParam.receive.rcvBuffLen; + case connectionClosing: + *errorCodePtr = errno = ESHUTDOWN; + statePtr->flags |= TCP_REMOTE_CLOSED; + return 0; + case connectionDoesntExist: + case connectionTerminated: + *errorCodePtr = errno = ENOTCONN; + statePtr->flags |= TCP_REMOTE_CLOSED; + return 0; + case invalidStreamPtr: + default: + *errorCodePtr = EINVAL; + return -1; + } + } + + /* + * No data is available, so check the connection state to + * see why this is the case. + */ + + if (statusPB.csParam.status.connectionState == 14) { + statePtr->flags |= TCP_REMOTE_CLOSED; + return 0; + } + if (statusPB.csParam.status.connectionState != 8) { + Debugger(); + } + statePtr->checkMask &= ~TCL_READABLE; + if (statePtr->flags & TCP_ASYNC_SOCKET) { + *errorCodePtr = EWOULDBLOCK; + return -1; + } + + /* + * In the blocking case, wait until the file becomes readable + * or closed and try again. + */ + + if (!WaitForSocketEvent(statePtr, TCL_READABLE, errorCodePtr)) { + return -1; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TcpGetHandle -- + * + * Called from Tcl_GetChannelFile to retrieve handles from inside + * a file based channel. + * + * Results: + * The appropriate handle or NULL if not present. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TcpGetHandle( + ClientData instanceData, /* The file state. */ + int direction, /* Which handle to retrieve? */ + ClientData *handlePtr) +{ + TcpState *statePtr = (TcpState *) instanceData; + + *handlePtr = (ClientData) statePtr->tcpStream; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TcpOutput-- + * + * Writes the given output on the IO channel. Returns count of how + * many characters were actually written, and an error indication. + * + * Results: + * A count of how many characters were written is returned and an + * error indication is returned in an output argument. + * + * Side effects: + * Writes output on the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +TcpOutput( + ClientData instanceData, /* Channel state. */ + char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCodePtr) /* Where to store error code. */ +{ + TcpState *statePtr = (TcpState *) instanceData; + StreamPtr tcpStream; + OSErr err; + int amount; + TCPiopb statusPB; + + *errorCodePtr = 0; + tcpStream = statePtr->tcpStream; + + /* + * If an asynchronous connect is in progress, attempt to wait for it + * to complete before writing. + */ + + if ((statePtr->flags & TCP_ASYNC_CONNECT) + && ! WaitForSocketEvent(statePtr, TCL_WRITABLE, errorCodePtr)) { + return -1; + } + + /* + * Loop until we have written some data, or an error occurs. + */ + + while (1) { + statusPB.ioCRefNum = driverRefNum; + statusPB.tcpStream = tcpStream; + statusPB.csCode = TCPStatus; + err = PBControlSync((ParmBlkPtr) &statusPB); + if ((err == connectionDoesntExist) || ((err == noErr) && + (statusPB.csParam.status.connectionState == 14))) { + /* + * The remote connection is gone away. Report an error + * and don't write anything. + */ + + *errorCodePtr = errno = EPIPE; + return -1; + } else if (err != noErr) { + return -1; + } + amount = statusPB.csParam.status.sendWindow + - statusPB.csParam.status.amtUnackedData; + + /* + * Attempt to write the data to the socket if a background + * write isn't in progress and there is room in the output buffers. + */ + + if (!(statePtr->flags & TCP_WRITING) && amount > 0) { + if (toWrite < amount) { + amount = toWrite; + } + statePtr->dataSegment[0].length = amount; + statePtr->dataSegment[0].ptr = buf; + statePtr->dataSegment[1].length = 0; + InitMacTCPParamBlock(&statePtr->pb, TCPSend); + statePtr->pb.ioCompletion = completeUPP; + statePtr->pb.tcpStream = tcpStream; + statePtr->pb.csParam.send.wdsPtr = (Ptr) statePtr->dataSegment; + statePtr->pb.csParam.send.pushFlag = 1; + statePtr->pb.csParam.send.userDataPtr = (Ptr) statePtr; + statePtr->flags |= TCP_WRITING; + err = PBControlAsync((ParmBlkPtr) &(statePtr->pb)); + switch (err) { + case noErr: + return amount; + case connectionClosing: + *errorCodePtr = errno = ESHUTDOWN; + statePtr->flags |= TCP_REMOTE_CLOSED; + return -1; + case connectionDoesntExist: + case connectionTerminated: + *errorCodePtr = errno = ENOTCONN; + statePtr->flags |= TCP_REMOTE_CLOSED; + return -1; + case invalidStreamPtr: + default: + return -1; + } + + } + + /* + * The socket wasn't writable. In the non-blocking case, return + * immediately, otherwise wait until the file becomes writable + * or closed and try again. + */ + + if (statePtr->flags & TCP_ASYNC_SOCKET) { + statePtr->checkMask &= ~TCL_WRITABLE; + *errorCodePtr = EWOULDBLOCK; + return -1; + } else if (!WaitForSocketEvent(statePtr, TCL_WRITABLE, errorCodePtr)) { + return -1; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TcpGetOptionProc -- + * + * Computes an option value for a TCP socket based channel, or a + * list of all options and their values. + * + * Note: This code is based on code contributed by John Haxby. + * + * Results: + * A standard Tcl result. The value of the specified option or a + * list of all options and their values is returned in the + * supplied DString. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TcpGetOptionProc( + ClientData instanceData, /* Socket state. */ + Tcl_Interp *interp, /* For error reporting - can be NULL.*/ + char *optionName, /* Name of the option to + * retrieve the value for, or + * NULL to get all options and + * their values. */ + Tcl_DString *dsPtr) /* Where to store the computed + * value; initialized by caller. */ +{ + TcpState *statePtr = (TcpState *) instanceData; + int doPeerName = false, doSockName = false, doAll = false; + ip_addr tcpAddress; + char buffer[128]; + OSErr err; + Tcl_DString dString; + TCPiopb statusPB; + int errorCode; + + /* + * If an asynchronous connect is in progress, attempt to wait for it + * to complete before accessing the socket state. + */ + + if ((statePtr->flags & TCP_ASYNC_CONNECT) + && ! WaitForSocketEvent(statePtr, TCL_WRITABLE, &errorCode)) { + if (interp) { + /* + * fix the error message. + */ + + Tcl_AppendResult(interp, "connect is in progress and can't wait", + NULL); + } + return TCL_ERROR; + } + + /* + * Determine which options we need to do. Do all of them + * if optionName is NULL. + */ + + if (optionName == (char *) NULL || optionName[0] == '\0') { + doAll = true; + } else { + if (!strcmp(optionName, "-peername")) { + doPeerName = true; + } else if (!strcmp(optionName, "-sockname")) { + doSockName = true; + } else { + return Tcl_BadChannelOption(interp, optionName, + "peername sockname"); + } + } + + /* + * Get status on the stream. Make sure to use a new pb struct because + * the struct in the statePtr may be part of an asyncronous call. + */ + + statusPB.ioCRefNum = driverRefNum; + statusPB.tcpStream = statePtr->tcpStream; + statusPB.csCode = TCPStatus; + err = PBControlSync((ParmBlkPtr) &statusPB); + if ((err == connectionDoesntExist) || + ((err == noErr) && (statusPB.csParam.status.connectionState == 14))) { + /* + * The socket was probably closed on the other side of the connection. + */ + + if (interp) { + Tcl_AppendResult(interp, "can't access socket info: ", + "connection reset by peer", NULL); + } + return TCL_ERROR; + } else if (err != noErr) { + if (interp) { + Tcl_AppendResult(interp, "unknown socket error", NULL); + } + Debugger(); + return TCL_ERROR; + } + + + /* + * Get the sockname for the socket. + */ + + Tcl_DStringInit(&dString); + if (doAll || doSockName) { + if (doAll) { + Tcl_DStringAppendElement(dsPtr, "-sockname"); + Tcl_DStringStartSublist(dsPtr); + } + tcpAddress = statusPB.csParam.status.localHost; + sprintf(buffer, "%d.%d.%d.%d", tcpAddress>>24, + tcpAddress>>16 & 0xff, tcpAddress>>8 & 0xff, + tcpAddress & 0xff); + Tcl_DStringAppendElement(dsPtr, buffer); + if (ResolveAddress(tcpAddress, &dString) == noErr) { + Tcl_DStringAppendElement(dsPtr, dString.string); + } else { + Tcl_DStringAppendElement(dsPtr, "<unknown>"); + } + sprintf(buffer, "%d", statusPB.csParam.status.localPort); + Tcl_DStringAppendElement(dsPtr, buffer); + if (doAll) { + Tcl_DStringEndSublist(dsPtr); + } + } + + /* + * Get the peername for the socket. + */ + + if ((doAll || doPeerName) && (statePtr->flags & TCP_CONNECTED)) { + if (doAll) { + Tcl_DStringAppendElement(dsPtr, "-peername"); + Tcl_DStringStartSublist(dsPtr); + } + tcpAddress = statusPB.csParam.status.remoteHost; + sprintf(buffer, "%d.%d.%d.%d", tcpAddress>>24, + tcpAddress>>16 & 0xff, tcpAddress>>8 & 0xff, + tcpAddress & 0xff); + Tcl_DStringAppendElement(dsPtr, buffer); + Tcl_DStringSetLength(&dString, 0); + if (ResolveAddress(tcpAddress, &dString) == noErr) { + Tcl_DStringAppendElement(dsPtr, dString.string); + } else { + Tcl_DStringAppendElement(dsPtr, "<unknown>"); + } + sprintf(buffer, "%d", statusPB.csParam.status.remotePort); + Tcl_DStringAppendElement(dsPtr, buffer); + if (doAll) { + Tcl_DStringEndSublist(dsPtr); + } + } + + Tcl_DStringFree(&dString); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TcpWatch -- + * + * Initialize the notifier to watch this channel. + * + * Results: + * None. + * + * Side effects: + * Sets the watchMask for the channel. + * + *---------------------------------------------------------------------- + */ + +static void +TcpWatch(instanceData, mask) + ClientData instanceData; /* The file state. */ + int mask; /* Events of interest; an OR-ed + * combination of TCL_READABLE, + * TCL_WRITABLE and TCL_EXCEPTION. */ +{ + TcpState *statePtr = (TcpState *) instanceData; + + statePtr->watchMask = mask; +} + +/* + *---------------------------------------------------------------------- + * + * NewSocketInfo -- + * + * This function allocates and initializes a new SocketInfo + * structure. + * + * Results: + * Returns a newly allocated SocketInfo. + * + * Side effects: + * Adds the socket to the global socket list, allocates memory. + * + *---------------------------------------------------------------------- + */ + +static TcpState * +NewSocketInfo( + StreamPtr tcpStream) +{ + TcpState *statePtr; + + statePtr = (TcpState *) ckalloc((unsigned) sizeof(TcpState)); + statePtr->tcpStream = tcpStream; + statePtr->psn = applicationPSN; + statePtr->flags = 0; + statePtr->checkMask = 0; + statePtr->watchMask = 0; + statePtr->acceptProc = (Tcl_TcpAcceptProc *) NULL; + statePtr->acceptProcData = (ClientData) NULL; + statePtr->nextPtr = socketList; + socketList = statePtr; + return statePtr; +} + +/* + *---------------------------------------------------------------------- + * + * FreeSocketInfo -- + * + * This function deallocates a SocketInfo structure that is no + * longer needed. + * + * Results: + * None. + * + * Side effects: + * Removes the socket from the global socket list, frees memory. + * + *---------------------------------------------------------------------- + */ + +static void +FreeSocketInfo( + TcpState *statePtr) /* The state pointer to free. */ +{ + if (statePtr == socketList) { + socketList = statePtr->nextPtr; + } else { + TcpState *p; + for (p = socketList; p != NULL; p = p->nextPtr) { + if (p->nextPtr == statePtr) { + p->nextPtr = statePtr->nextPtr; + break; + } + } + } + ckfree((char *) statePtr); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_MakeTcpClientChannel -- + * + * Creates a Tcl_Channel from an existing client TCP socket. + * + * Results: + * The Tcl_Channel wrapped around the preexisting TCP socket. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +Tcl_MakeTcpClientChannel( + ClientData sock) /* The socket to wrap up into a channel. */ +{ + TcpState *statePtr; + char channelName[20]; + + if (TclHasSockets(NULL) != TCL_OK) { + return NULL; + } + + statePtr = NewSocketInfo((StreamPtr) sock); + /* TODO: do we need to set the port??? */ + + sprintf(channelName, "sock%d", socketNumber++); + + statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + (ClientData) statePtr, (TCL_READABLE | TCL_WRITABLE)); + Tcl_SetChannelBufferSize(statePtr->channel, socketBufferSize); + Tcl_SetChannelOption(NULL, statePtr->channel, "-translation", "auto crlf"); + return statePtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * CreateSocket -- + * + * This function opens a new socket and initializes the + * SocketInfo structure. + * + * Results: + * Returns a new SocketInfo, or NULL with an error in interp. + * + * Side effects: + * Adds a new socket to the socketList. + * + *---------------------------------------------------------------------- + */ + +static TcpState * +CreateSocket( + Tcl_Interp *interp, /* For error reporting; can be NULL. */ + int port, /* Port number to open. */ + char *host, /* Name of host on which to open port. */ + char *myaddr, /* Optional client-side address */ + int myport, /* Optional client-side port */ + int server, /* 1 if socket should be a server socket, + * else 0 for a client socket. */ + int async) /* 1 create async, 0 do sync. */ +{ + ip_addr macAddr; + OSErr err; + TCPiopb pb; + StreamPtr tcpStream; + TcpState *statePtr; + char * buffer; + + /* + * Figure out the ip address from the host string. + */ + + if (host == NULL) { + err = GetLocalAddress(&macAddr); + } else { + err = GetHostFromString(host, &macAddr); + } + if (err != noErr) { + Tcl_SetErrno(EHOSTUNREACH); + if (interp != (Tcl_Interp *) NULL) { + Tcl_AppendResult(interp, "couldn't open socket: ", + Tcl_PosixError(interp), (char *) NULL); + } + return (TcpState *) NULL; + } + + /* + * Create a MacTCP stream and create the state used for socket + * transactions from here on out. + */ + + ClearZombieSockets(); + buffer = ckalloc(socketBufferSize); + InitMacTCPParamBlock(&pb, TCPCreate); + pb.csParam.create.rcvBuff = buffer; + pb.csParam.create.rcvBuffLen = socketBufferSize; + err = PBControlSync((ParmBlkPtr) &pb); + if (err != noErr) { + Tcl_SetErrno(0); /* TODO: set to ENOSR - maybe?*/ + if (interp != (Tcl_Interp *) NULL) { + Tcl_AppendResult(interp, "couldn't open socket: ", + Tcl_PosixError(interp), (char *) NULL); + } + return (TcpState *) NULL; + } + + tcpStream = pb.tcpStream; + statePtr = NewSocketInfo(tcpStream); + statePtr->port = port; + + if (server) { + /* + * Set up server connection. + */ + + InitMacTCPParamBlock(&statePtr->pb, TCPPassiveOpen); + statePtr->pb.tcpStream = tcpStream; + statePtr->pb.csParam.open.localPort = statePtr->port; + statePtr->pb.ioCompletion = completeUPP; + statePtr->pb.csParam.open.userDataPtr = (Ptr) statePtr; + statePtr->flags |= TCP_LISTENING; + err = PBControlAsync((ParmBlkPtr) &(statePtr->pb)); + + /* + * If this is a server on port 0 then we need to wait until + * the dynamic port allocation is made by the MacTcp driver. + */ + + if (statePtr->port == 0) { + EventRecord dummy; + + while (statePtr->pb.csParam.open.localPort == 0) { + WaitNextEvent(0, &dummy, 1, NULL); + if (statePtr->pb.ioResult != 0) { + break; + } + } + statePtr->port = statePtr->pb.csParam.open.localPort; + } + Tcl_SetErrno(EINPROGRESS); + } else { + /* + * Attempt to connect. The connect may fail at present with an + * EINPROGRESS but at a later time it will complete. The caller + * will set up a file handler on the socket if she is interested in + * being informed when the connect completes. + */ + + InitMacTCPParamBlock(&statePtr->pb, TCPActiveOpen); + statePtr->pb.tcpStream = tcpStream; + statePtr->pb.csParam.open.remoteHost = macAddr; + statePtr->pb.csParam.open.remotePort = port; + statePtr->pb.csParam.open.localHost = 0; + statePtr->pb.csParam.open.localPort = myport; + statePtr->pb.csParam.open.userDataPtr = (Ptr) statePtr; + statePtr->pb.ioCompletion = completeUPP; + if (async) { + statePtr->flags |= TCP_ASYNC_CONNECT; + err = PBControlAsync((ParmBlkPtr) &(statePtr->pb)); + Tcl_SetErrno(EINPROGRESS); + } else { + err = PBControlSync((ParmBlkPtr) &(statePtr->pb)); + } + } + + switch (err) { + case noErr: + if (!async) { + statePtr->flags |= TCP_CONNECTED; + } + return statePtr; + case duplicateSocket: + Tcl_SetErrno(EADDRINUSE); + break; + case openFailed: + case connectionTerminated: + Tcl_SetErrno(ECONNREFUSED); + break; + case invalidStreamPtr: + case connectionExists: + default: + /* + * These cases should never occur. However, we will fail + * gracefully and hope Tcl can resume. The alternative is to panic + * which is probably a bit drastic. + */ + + Debugger(); + Tcl_SetErrno(err); + } + + /* + * We had error during the connection. Release the stream + * and file handle. Also report to the interp. + */ + + pb.ioCRefNum = driverRefNum; + pb.csCode = TCPRelease; + pb.tcpStream = tcpStream; + pb.ioCompletion = NULL; + err = PBControlSync((ParmBlkPtr) &pb); + + if (interp != (Tcl_Interp *) NULL) { + Tcl_AppendResult(interp, "couldn't open socket: ", + Tcl_PosixError(interp), (char *) NULL); + } + + ckfree(buffer); + FreeSocketInfo(statePtr); + return (TcpState *) NULL; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_OpenTcpClient -- + * + * Opens a TCP client socket and creates a channel around it. + * + * Results: + * The channel or NULL if failed. On failure, the routine also + * sets the output argument errorCodePtr to the error code. + * + * Side effects: + * Opens a client socket and creates a new channel. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +Tcl_OpenTcpClient( + Tcl_Interp *interp, /* For error reporting; can be NULL. */ + int port, /* Port number to open. */ + char *host, /* Host on which to open port. */ + char *myaddr, /* Client-side address */ + int myport, /* Client-side port */ + int async) /* If nonzero, attempt to do an + * asynchronous connect. Otherwise + * we do a blocking connect. + * - currently ignored */ +{ + TcpState *statePtr; + char channelName[20]; + + if (TclHasSockets(interp) != TCL_OK) { + return NULL; + } + + /* + * Create a new client socket and wrap it in a channel. + */ + + statePtr = CreateSocket(interp, port, host, myaddr, myport, 0, async); + if (statePtr == NULL) { + return NULL; + } + + sprintf(channelName, "sock%d", socketNumber++); + + statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + (ClientData) statePtr, (TCL_READABLE | TCL_WRITABLE)); + Tcl_SetChannelBufferSize(statePtr->channel, socketBufferSize); + Tcl_SetChannelOption(NULL, statePtr->channel, "-translation", "auto crlf"); + return statePtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_OpenTcpServer -- + * + * Opens a TCP server socket and creates a channel around it. + * + * Results: + * The channel or NULL if failed. + * + * Side effects: + * Opens a server socket and creates a new channel. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +Tcl_OpenTcpServer( + Tcl_Interp *interp, /* For error reporting - may be + * NULL. */ + int port, /* Port number to open. */ + char *host, /* Name of local host. */ + Tcl_TcpAcceptProc *acceptProc, /* Callback for accepting connections + * from new clients. */ + ClientData acceptProcData) /* Data for the callback. */ +{ + TcpState *statePtr; + char channelName[20]; + + if (TclHasSockets(interp) != TCL_OK) { + return NULL; + } + + /* + * Create a new client socket and wrap it in a channel. + */ + + statePtr = CreateSocket(interp, port, host, NULL, 0, 1, 1); + if (statePtr == NULL) { + return NULL; + } + + statePtr->acceptProc = acceptProc; + statePtr->acceptProcData = acceptProcData; + + sprintf(channelName, "sock%d", socketNumber++); + + statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + (ClientData) statePtr, 0); + Tcl_SetChannelBufferSize(statePtr->channel, socketBufferSize); + Tcl_SetChannelOption(NULL, statePtr->channel, "-translation", "auto crlf"); + return statePtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * SocketEventProc -- + * + * This procedure is called by Tcl_ServiceEvent when a socket event + * reaches the front of the event queue. This procedure is + * responsible for notifying the generic channel code. + * + * Results: + * Returns 1 if the event was handled, meaning it should be removed + * from the queue. Returns 0 if the event was not handled, meaning + * it should stay on the queue. The only time the event isn't + * handled is if the TCL_FILE_EVENTS flag bit isn't set. + * + * Side effects: + * Whatever the channel callback procedures do. + * + *---------------------------------------------------------------------- + */ + +static int +SocketEventProc( + Tcl_Event *evPtr, /* Event to service. */ + int flags) /* Flags that indicate what events to + * handle, such as TCL_FILE_EVENTS. */ +{ + TcpState *statePtr; + SocketEvent *eventPtr = (SocketEvent *) evPtr; + int mask = 0; + + if (!(flags & TCL_FILE_EVENTS)) { + return 0; + } + + /* + * Find the specified socket on the socket list. + */ + + for (statePtr = socketList; statePtr != NULL; + statePtr = statePtr->nextPtr) { + if ((statePtr == eventPtr->statePtr) && + (statePtr->tcpStream == eventPtr->tcpStream)) { + break; + } + } + + /* + * Discard events that have gone stale. + */ + + if (!statePtr) { + return 1; + } + statePtr->flags &= ~(TCP_PENDING); + if (statePtr->flags & TCP_RELEASE) { + SocketFreeProc(statePtr); + return 1; + } + + + /* + * Handle connection requests directly. + */ + + if (statePtr->flags & TCP_LISTEN_CONNECT) { + if (statePtr->checkMask & TCL_READABLE) { + TcpAccept(statePtr); + } + return 1; + } + + /* + * Mask off unwanted events then notify the channel. + */ + + mask = statePtr->checkMask & statePtr->watchMask; + if (mask) { + Tcl_NotifyChannel(statePtr->channel, mask); + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * WaitForSocketEvent -- + * + * Waits until one of the specified events occurs on a socket. + * + * Results: + * Returns 1 on success or 0 on failure, with an error code in + * errorCodePtr. + * + * Side effects: + * Processes socket events off the system queue. + * + *---------------------------------------------------------------------- + */ + +static int +WaitForSocketEvent( + TcpState *statePtr, /* Information about this socket. */ + int mask, /* Events to look for. */ + int *errorCodePtr) /* Where to store errors? */ +{ + OSErr err; + TCPiopb statusPB; + EventRecord dummy; + + /* + * Loop until we get the specified condition, unless the socket is + * asynchronous. + */ + + do { + statusPB.ioCRefNum = driverRefNum; + statusPB.tcpStream = statePtr->tcpStream; + statusPB.csCode = TCPStatus; + err = PBControlSync((ParmBlkPtr) &statusPB); + if (err != noErr) { + statePtr->checkMask |= (TCL_READABLE | TCL_WRITABLE); + return 1; + } + statePtr->checkMask = 0; + if (statusPB.csParam.status.amtUnreadData > 0) { + statePtr->checkMask |= TCL_READABLE; + } + if (!(statePtr->flags & TCP_WRITING) + && (statusPB.csParam.status.sendWindow - + statusPB.csParam.status.amtUnackedData) > 0) { + statePtr->flags &= ~(TCP_ASYNC_CONNECT); + statePtr->checkMask |= TCL_WRITABLE; + } + if (mask & statePtr->checkMask) { + return 1; + } + + /* + * Call the system to let other applications run while we + * are waiting for this event to occur. + */ + + WaitNextEvent(0, &dummy, 1, NULL); + } while (!(statePtr->flags & TCP_ASYNC_SOCKET)); + *errorCodePtr = EWOULDBLOCK; + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TcpAccept -- + * Accept a TCP socket connection. This is called by the event + * loop, and it in turns calls any registered callbacks for this + * channel. + * + * Results: + * None. + * + * Side effects: + * Evals the Tcl script associated with the server socket. + * + *---------------------------------------------------------------------- + */ + +static void +TcpAccept( + TcpState *statePtr) +{ + TcpState *newStatePtr; + StreamPtr tcpStream; + char remoteHostname[255]; + OSErr err; + ip_addr remoteAddress; + long remotePort; + char channelName[20]; + + statePtr->flags &= ~TCP_LISTEN_CONNECT; + statePtr->checkMask &= ~TCL_READABLE; + + /* + * Transfer sever stream to new connection. + */ + + tcpStream = statePtr->tcpStream; + newStatePtr = NewSocketInfo(tcpStream); + newStatePtr->tcpStream = tcpStream; + sprintf(channelName, "sock%d", socketNumber++); + + + newStatePtr->flags |= TCP_CONNECTED; + newStatePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + (ClientData) newStatePtr, (TCL_READABLE | TCL_WRITABLE)); + Tcl_SetChannelBufferSize(newStatePtr->channel, socketBufferSize); + Tcl_SetChannelOption(NULL, newStatePtr->channel, "-translation", + "auto crlf"); + + remoteAddress = statePtr->pb.csParam.open.remoteHost; + remotePort = statePtr->pb.csParam.open.remotePort; + + /* + * Reopen passive connect. Make new tcpStream the server. + */ + + ClearZombieSockets(); + InitMacTCPParamBlock(&statePtr->pb, TCPCreate); + statePtr->pb.csParam.create.rcvBuff = ckalloc(socketBufferSize); + statePtr->pb.csParam.create.rcvBuffLen = socketBufferSize; + err = PBControlSync((ParmBlkPtr) &statePtr->pb); + if (err != noErr) { + /* + * Hmmm... We can't reopen the server. We'll go ahead + * an continue - but we are kind of broken now... + */ + Debugger(); + statePtr->tcpStream = -1; + statePtr->flags |= TCP_SERVER_ZOMBIE; + } + + tcpStream = statePtr->tcpStream = statePtr->pb.tcpStream; + + InitMacTCPParamBlock(&statePtr->pb, TCPPassiveOpen); + statePtr->pb.tcpStream = tcpStream; + statePtr->pb.csParam.open.localHost = 0; + statePtr->pb.csParam.open.localPort = statePtr->port; + statePtr->pb.ioCompletion = completeUPP; + statePtr->pb.csParam.open.userDataPtr = (Ptr) statePtr; + statePtr->flags |= TCP_LISTENING; + err = PBControlAsync((ParmBlkPtr) &(statePtr->pb)); + /* + * TODO: deal with case where we can't recreate server socket... + */ + + /* + * Finally we run the accept procedure. We must do this last to make + * sure we are in a nice clean state. This Tcl code can do anything + * including closing the server or client sockets we've just delt with. + */ + + if (statePtr->acceptProc != NULL) { + sprintf(remoteHostname, "%d.%d.%d.%d", remoteAddress>>24, + remoteAddress>>16 & 0xff, remoteAddress>>8 & 0xff, + remoteAddress & 0xff); + + (statePtr->acceptProc)(statePtr->acceptProcData, newStatePtr->channel, + remoteHostname, remotePort); + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_GetHostName -- + * + * Returns the name of the local host. + * + * Results: + * A string containing the network name for this machine, or + * an empty string if we can't figure out the name. The caller + * must not modify or free this string. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +char * +Tcl_GetHostName() +{ + static int hostnameInited = 0; + static char hostname[255]; + ip_addr ourAddress; + Tcl_DString dString; + OSErr err; + + if (hostnameInited) { + return hostname; + } + + if (TclHasSockets(NULL) == TCL_OK) { + err = GetLocalAddress(&ourAddress); + if (err == noErr) { + /* + * Search for the doman name and return it if found. Otherwise, + * just print the IP number to a string and return that. + */ + + Tcl_DStringInit(&dString); + err = ResolveAddress(ourAddress, &dString); + if (err == noErr) { + strcpy(hostname, dString.string); + } else { + sprintf(hostname, "%d.%d.%d.%d", ourAddress>>24, ourAddress>>16 & 0xff, + ourAddress>>8 & 0xff, ourAddress & 0xff); + } + Tcl_DStringFree(&dString); + + hostnameInited = 1; + return hostname; + } + } + + hostname[0] = '\0'; + hostnameInited = 1; + return hostname; +} + +/* + *---------------------------------------------------------------------- + * + * ResolveAddress -- + * + * This function is used to resolve an ip address to it's full + * domain name address. + * + * Results: + * An os err value. + * + * Side effects: + * Treats client data as int we set to true. + * + *---------------------------------------------------------------------- + */ + +static OSErr +ResolveAddress( + ip_addr tcpAddress, /* Address to resolve. */ + Tcl_DString *dsPtr) /* Returned address in string. */ +{ + int i; + EventRecord dummy; + DNRState dnrState; + OSErr err; + + /* + * Call AddrToName to resolve our ip address to our domain name. + * The call is async, so we must wait for a callback to tell us + * when to continue. + */ + + for (i = 0; i < NUM_ALT_ADDRS; i++) { + dnrState.hostInfo.addr[i] = 0; + } + dnrState.done = 0; + GetCurrentProcess(&(dnrState.psn)); + err = AddrToName(tcpAddress, &dnrState.hostInfo, resultUPP, (Ptr) &dnrState); + if (err == cacheFault) { + while (!dnrState.done) { + WaitNextEvent(0, &dummy, 1, NULL); + } + } + + /* + * If there is no error in finding the domain name we set the + * result into the dynamic string. We also work around a bug in + * MacTcp where an extranious '.' may be found at the end of the name. + */ + + if (dnrState.hostInfo.rtnCode == noErr) { + i = strlen(dnrState.hostInfo.cname) - 1; + if (dnrState.hostInfo.cname[i] == '.') { + dnrState.hostInfo.cname[i] = '\0'; + } + Tcl_DStringAppend(dsPtr, dnrState.hostInfo.cname, -1); + } + + return dnrState.hostInfo.rtnCode; +} + +/* + *---------------------------------------------------------------------- + * + * DNRCompletionRoutine -- + * + * This function is called when the Domain Name Server is done + * seviceing our request. It just sets a flag that we can poll + * in functions like Tcl_GetHostName to let them know to continue. + * + * Results: + * None. + * + * Side effects: + * Treats client data as int we set to true. + * + *---------------------------------------------------------------------- + */ + +static pascal void +DNRCompletionRoutine( + struct hostInfo *hostinfoPtr, /* Host infor struct. */ + DNRState *dnrStatePtr) /* Completetion state. */ +{ + dnrStatePtr->done = true; + WakeUpProcess(&(dnrStatePtr->psn)); +} + +/* + *---------------------------------------------------------------------- + * + * CleanUpExitProc -- + * + * This procedure is invoked as an exit handler when ExitToShell + * is called. It aborts any lingering socket connections. This + * must be called or the Mac OS will more than likely crash. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static pascal void +CleanUpExitProc() +{ + TCPiopb exitPB; + TcpState *statePtr; + + while (socketList != NULL) { + statePtr = socketList; + socketList = statePtr->nextPtr; + + /* + * Close and Release the connection. + */ + + exitPB.ioCRefNum = driverRefNum; + exitPB.csCode = TCPClose; + exitPB.tcpStream = statePtr->tcpStream; + exitPB.csParam.close.ulpTimeoutValue = 60 /* seconds */; + exitPB.csParam.close.ulpTimeoutAction = 1 /* 1:abort 0:report */; + exitPB.csParam.close.validityFlags = timeoutValue | timeoutAction; + exitPB.ioCompletion = NULL; + PBControlSync((ParmBlkPtr) &exitPB); + + exitPB.ioCRefNum = driverRefNum; + exitPB.csCode = TCPRelease; + exitPB.tcpStream = statePtr->tcpStream; + exitPB.ioCompletion = NULL; + PBControlSync((ParmBlkPtr) &exitPB); + } +} + +/* + *---------------------------------------------------------------------- + * + * GetHostFromString -- + * + * Looks up the passed in domain name in the domain resolver. It + * can accept strings of two types: 1) the ip number in string + * format, or 2) the domain name. + * + * Results: + * We return a ip address or 0 if there was an error or the + * domain does not exist. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static OSErr +GetHostFromString( + char *name, /* Host in string form. */ + ip_addr *address) /* Returned IP address. */ +{ + OSErr err; + int i; + EventRecord dummy; + DNRState dnrState; + + if (TclHasSockets(NULL) != TCL_OK) { + return 0; + } + + /* + * Call StrToAddr to get the ip number for the passed in domain + * name. The call is async, so we must wait for a callback to + * tell us when to continue. + */ + + for (i = 0; i < NUM_ALT_ADDRS; i++) { + dnrState.hostInfo.addr[i] = 0; + } + dnrState.done = 0; + GetCurrentProcess(&(dnrState.psn)); + err = StrToAddr(name, &dnrState.hostInfo, resultUPP, (Ptr) &dnrState); + if (err == cacheFault) { + while (!dnrState.done) { + WaitNextEvent(0, &dummy, 1, NULL); + } + } + + /* + * For some reason MacTcp may return a cachFault a second time via + * the hostinfo block. This seems to be a bug in MacTcp. In this case + * we run StrToAddr again - which seems to then work just fine. + */ + + if (dnrState.hostInfo.rtnCode == cacheFault) { + dnrState.done = 0; + err = StrToAddr(name, &dnrState.hostInfo, resultUPP, (Ptr) &dnrState); + if (err == cacheFault) { + while (!dnrState.done) { + WaitNextEvent(0, &dummy, 1, NULL); + } + } + } + + if (dnrState.hostInfo.rtnCode == noErr) { + *address = dnrState.hostInfo.addr[0]; + } + + return dnrState.hostInfo.rtnCode; +} + +/* + *---------------------------------------------------------------------- + * + * IOCompletionRoutine -- + * + * This function is called when an asynchronous socket operation + * completes. Since this routine runs as an interrupt handler, + * it will simply set state to tell the notifier that this socket + * is now ready for action. Note that this function is running at + * interupt time and can't allocate memory or do much else except + * set state. + * + * Results: + * None. + * + * Side effects: + * Sets some state in the socket state. May also wake the process + * if we are not currently running. + * + *---------------------------------------------------------------------- + */ + +static void +IOCompletionRoutine( + TCPiopb *pbPtr) /* Tcp parameter block. */ +{ + TcpState *statePtr; + + if (pbPtr->csCode == TCPSend) { + statePtr = (TcpState *) pbPtr->csParam.send.userDataPtr; + } else { + statePtr = (TcpState *) pbPtr->csParam.open.userDataPtr; + } + + /* + * Always wake the process in case it's in WaitNextEvent. + * If an error has a occured - just return. We will deal + * with the problem later. + */ + + WakeUpProcess(&statePtr->psn); + if (pbPtr->ioResult != noErr) { + return; + } + + if (statePtr->flags & TCP_ASYNC_CONNECT) { + statePtr->flags &= ~TCP_ASYNC_CONNECT; + statePtr->flags |= TCP_CONNECTED; + statePtr->checkMask |= TCL_READABLE & TCL_WRITABLE; + } else if (statePtr->flags & TCP_LISTENING) { + if (statePtr->port == 0) { + Debugger(); + } + statePtr->flags &= ~TCP_LISTENING; + statePtr->flags |= TCP_LISTEN_CONNECT; + statePtr->checkMask |= TCL_READABLE; + } else if (statePtr->flags & TCP_WRITING) { + statePtr->flags &= ~TCP_WRITING; + statePtr->checkMask |= TCL_WRITABLE; + if (!(statePtr->flags & TCP_CONNECTED)) { + InitMacTCPParamBlock(&statePtr->pb, TCPClose); + statePtr->pb.tcpStream = statePtr->tcpStream; + statePtr->pb.ioCompletion = closeUPP; + statePtr->pb.csParam.close.userDataPtr = (Ptr) statePtr; + if (PBControlAsync((ParmBlkPtr) &statePtr->pb) != noErr) { + statePtr->flags |= TCP_RELEASE; + } + } + } +} + +/* + *---------------------------------------------------------------------- + * + * GetLocalAddress -- + * + * Get the IP address for this machine. The result is cached so + * the result is returned quickly after the first call. + * + * Results: + * Macintosh error code. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static OSErr +GetLocalAddress( + unsigned long *addr) /* Returns host IP address. */ +{ + struct GetAddrParamBlock pBlock; + OSErr err = noErr; + static unsigned long localAddress = 0; + + if (localAddress == 0) { + memset(&pBlock, 0, sizeof(pBlock)); + pBlock.ioResult = 1; + pBlock.csCode = ipctlGetAddr; + pBlock.ioCRefNum = driverRefNum; + err = PBControlSync((ParmBlkPtr) &pBlock); + + if (err != noErr) { + return err; + } + localAddress = pBlock.ourAddress; + } + + *addr = localAddress; + return noErr; +} + +/* + *---------------------------------------------------------------------- + * + * GetBufferSize -- + * + * Get the appropiate buffer size for our machine & network. This + * value will be used by the rest of Tcl & the MacTcp driver for + * the size of its buffers. If out method for determining the + * optimal buffer size fails for any reason - we return a + * reasonable default. + * + * Results: + * Size of optimal buffer in bytes. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static long +GetBufferSize() +{ + UDPiopb iopb; + OSErr err = noErr; + long bufferSize; + + memset(&iopb, 0, sizeof(iopb)); + err = GetLocalAddress(&iopb.csParam.mtu.remoteHost); + if (err != noErr) { + return CHANNEL_BUF_SIZE; + } + iopb.ioCRefNum = driverRefNum; + iopb.csCode = UDPMaxMTUSize; + err = PBControlSync((ParmBlkPtr)&iopb); + if (err != noErr) { + return CHANNEL_BUF_SIZE; + } + bufferSize = (iopb.csParam.mtu.mtuSize * 4) + 1024; + if (bufferSize < CHANNEL_BUF_SIZE) { + bufferSize = CHANNEL_BUF_SIZE; + } + return bufferSize; +} + +/* + *---------------------------------------------------------------------- + * + * TclSockGetPort -- + * + * Maps from a string, which could be a service name, to a port. + * Used by socket creation code to get port numbers and resolve + * registered service names to port numbers. + * + * Results: + * A standard Tcl result. On success, the port number is + * returned in portPtr. On failure, an error message is left in + * interp->result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclSockGetPort( + Tcl_Interp *interp, /* Interp for error messages. */ + char *string, /* Integer or service name */ + char *proto, /* "tcp" or "udp", typically - + * ignored on Mac - assumed to be tcp */ + int *portPtr) /* Return port number */ +{ + PortInfo *portInfoPtr = NULL; + + if (Tcl_GetInt(interp, string, portPtr) == TCL_OK) { + if (*portPtr > 0xFFFF) { + Tcl_AppendResult(interp, "couldn't open socket: port number too high", + (char *) NULL); + return TCL_ERROR; + } + if (*portPtr < 0) { + Tcl_AppendResult(interp, "couldn't open socket: negative port number", + (char *) NULL); + return TCL_ERROR; + } + return TCL_OK; + } + for (portInfoPtr = portServices; portInfoPtr->name != NULL; portInfoPtr++) { + if (!strcmp(portInfoPtr->name, string)) { + break; + } + } + if (portInfoPtr != NULL && portInfoPtr->name != NULL) { + *portPtr = portInfoPtr->port; + Tcl_ResetResult(interp); + return TCL_OK; + } + + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * ClearZombieSockets -- + * + * This procedure looks through the socket list and removes the + * first stream it finds that is ready for release. This procedure + * should be called before we ever try to create new Tcp streams + * to ensure we can least allocate one stream. + * + * Results: + * None. + * + * Side effects: + * Tcp streams may be released. + * + *---------------------------------------------------------------------- + */ + +static void +ClearZombieSockets() +{ + TcpState *statePtr; + + for (statePtr = socketList; statePtr != NULL; + statePtr = statePtr->nextPtr) { + if (statePtr->flags & TCP_RELEASE) { + SocketFreeProc(statePtr); + return; + } + } +} diff --git a/tcl/mac/tclMacTest.c b/tcl/mac/tclMacTest.c new file mode 100644 index 00000000000..06a66ba62c0 --- /dev/null +++ b/tcl/mac/tclMacTest.c @@ -0,0 +1,242 @@ +/* + * tclMacTest.c -- + * + * Contains commands for platform specific tests for + * the Macintosh platform. + * + * Copyright (c) 1996 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#define TCL_TEST + +#include "tclInt.h" +#include "tclMacInt.h" +#include "tclMacPort.h" +#include "Files.h" +#include <Errors.h> +#include <Resources.h> +#include <Script.h> +#include <Strings.h> +#include <FSpCompat.h> + +/* + * Forward declarations of procedures defined later in this file: + */ + +int TclplatformtestInit _ANSI_ARGS_((Tcl_Interp *interp)); +static int DebuggerCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int argc, char **argv)); +static int WriteTextResource _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int argc, char **argv)); + + +/* + *---------------------------------------------------------------------- + * + * TclplatformtestInit -- + * + * Defines commands that test platform specific functionality for + * Unix platforms. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Defines new commands. + * + *---------------------------------------------------------------------- + */ + +int +TclplatformtestInit( + Tcl_Interp *interp) /* Interpreter to add commands to. */ +{ + /* + * Add commands for platform specific tests on MacOS here. + */ + + Tcl_CreateCommand(interp, "debugger", DebuggerCmd, + (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateCommand(interp, "testWriteTextResource", WriteTextResource, + (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * DebuggerCmd -- + * + * This procedure simply calls the low level debugger. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +DebuggerCmd( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + int argc, /* Not used. */ + char **argv) /* Not used. */ +{ + Debugger(); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * WriteTextResource -- + * + * This procedure will write a text resource out to the + * application or a given file. The format for this command is + * textwriteresource + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +WriteTextResource( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int argc, /* Number of arguments. */ + char **argv) /* Argument strings. */ +{ + char *errNum = "wrong # args: "; + char *errBad = "bad argument: "; + char *errStr; + char *fileName = NULL, *rsrcName = NULL; + char *data = NULL; + int rsrcID = -1, i, protectIt = 0; + short fileRef = -1; + OSErr err; + Handle dataHandle; + Str255 resourceName; + FSSpec fileSpec; + + /* + * Process the arguments. + */ + for (i = 1 ; i < argc ; i++) { + if (!strcmp(argv[i], "-rsrc")) { + rsrcName = argv[i + 1]; + i++; + } else if (!strcmp(argv[i], "-rsrcid")) { + rsrcID = atoi(argv[i + 1]); + i++; + } else if (!strcmp(argv[i], "-file")) { + fileName = argv[i + 1]; + i++; + } else if (!strcmp(argv[i], "-protected")) { + protectIt = 1; + } else { + data = argv[i]; + } + } + + if ((rsrcName == NULL && rsrcID < 0) || + (fileName == NULL) || (data == NULL)) { + errStr = errBad; + goto sourceFmtErr; + } + + /* + * Open the resource file. + */ + err = FSpLocationFromPath(strlen(fileName), fileName, &fileSpec); + if (!(err == noErr || err == fnfErr)) { + Tcl_AppendResult(interp, "couldn't validate file name", (char *) NULL); + return TCL_ERROR; + } + + if (err == fnfErr) { + FSpCreateResFile(&fileSpec, 'WIsH', 'rsrc', smSystemScript); + } + fileRef = FSpOpenResFile(&fileSpec, fsRdWrPerm); + if (fileRef == -1) { + Tcl_AppendResult(interp, "couldn't open resource file", (char *) NULL); + return TCL_ERROR; + } + + UseResFile(fileRef); + + /* + * Prepare data needed to create resource. + */ + if (rsrcID < 0) { + rsrcID = UniqueID('TEXT'); + } + + strcpy((char *) resourceName, rsrcName); + c2pstr((char *) resourceName); + + dataHandle = NewHandle(strlen(data)); + HLock(dataHandle); + strcpy(*dataHandle, data); + HUnlock(dataHandle); + + /* + * Add the resource to the file and close it. + */ + AddResource(dataHandle, 'TEXT', rsrcID, resourceName); + + UpdateResFile(fileRef); + if (protectIt) { + SetResAttrs(Get1Resource('TEXT', rsrcID), resProtected); + } + + CloseResFile(fileRef); + return TCL_OK; + + sourceFmtErr: + Tcl_AppendResult(interp, errStr, "error in \"", argv[0], "\"", + (char *) NULL); + return TCL_ERROR; +} + +int +TclMacChmod( + char *path, + int mode) +{ + HParamBlockRec hpb; + OSErr err; + + c2pstr(path); + hpb.fileParam.ioNamePtr = (unsigned char *) path; + hpb.fileParam.ioVRefNum = 0; + hpb.fileParam.ioDirID = 0; + + if (mode & 0200) { + err = PBHRstFLockSync(&hpb); + } else { + err = PBHSetFLockSync(&hpb); + } + p2cstr((unsigned char *) path); + + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + return -1; + } + + return 0; +} + diff --git a/tcl/mac/tclMacTime.c b/tcl/mac/tclMacTime.c new file mode 100644 index 00000000000..f27a0425568 --- /dev/null +++ b/tcl/mac/tclMacTime.c @@ -0,0 +1,312 @@ +/* + * tclMacTime.c -- + * + * Contains Macintosh specific versions of Tcl functions that + * obtain time values from the operating system. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tclInt.h" +#include "tclPort.h" +#include <OSUtils.h> +#include <Timer.h> +#include <time.h> + +/* + * Static variables used by the TclpGetTime function. + */ + +static int initalized = false; +static unsigned long baseSeconds; +static UnsignedWide microOffset; + +/* + * Prototypes for procedures that are private to this file: + */ + +static void SubtractUnsignedWide _ANSI_ARGS_((UnsignedWide *x, + UnsignedWide *y, UnsignedWide *result)); + +/* + *----------------------------------------------------------------------------- + * + * TclpGetSeconds -- + * + * This procedure returns the number of seconds from the epoch. On + * the Macintosh the epoch is Midnight Jan 1, 1904. Unfortunatly, + * the Macintosh doesn't tie the epoch to a particular time zone. For + * Tcl we tie the epoch to GMT. This makes the time zone date parsing + * code work. The epoch for Mac-Tcl is: Midnight Jan 1, 1904 GMT. + * + * Results: + * Number of seconds from the epoch in GMT. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +unsigned long +TclpGetSeconds() +{ + unsigned long seconds; + MachineLocation loc; + long int offset; + + ReadLocation(&loc); + offset = loc.u.gmtDelta & 0x00ffffff; + if (offset & 0x00800000) { + offset = offset | 0xff000000; + } + + if (ReadDateTime(&seconds) == noErr) { + return (seconds - offset); + } else { + panic("Can't get time."); + return 0; + } +} + +/* + *----------------------------------------------------------------------------- + * + * TclpGetClicks -- + * + * This procedure returns a value that represents the highest resolution + * clock available on the system. There are no garantees on what the + * resolution will be. In Tcl we will call this value a "click". The + * start time is also system dependant. + * + * Results: + * Number of clicks from some start time. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +unsigned long +TclpGetClicks() +{ + UnsignedWide micros; + + Microseconds(µs); + return micros.lo; +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetTimeZone -- + * + * Get the current time zone. + * + * Results: + * The return value is the local time zone, measured in + * minutes away from GMT (-ve for east, +ve for west). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclpGetTimeZone ( + unsigned long currentTime) /* Ignored on Mac. */ +{ + MachineLocation loc; + long int offset; + + ReadLocation(&loc); + offset = loc.u.gmtDelta & 0x00ffffff; + if (offset & 0x00700000) { + offset |= 0xff000000; + } + + /* + * Convert the Mac offset from seconds to minutes and + * add an hour if we have daylight savings time. + */ + offset = -offset; + offset /= 60; + if (loc.u.dlsDelta < 0) { + offset += 60; + } + + return offset; +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetTime -- + * + * Gets the current system time in seconds and microseconds + * since the beginning of the epoch: 00:00 UCT, January 1, 1970. + * + * Results: + * Returns the current time (in the local timezone) in timePtr. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclpGetTime( + Tcl_Time *timePtr) /* Location to store time information. */ +{ + UnsignedWide micro; +#ifndef NO_LONG_LONG + long long *microPtr; +#endif + + if (initalized == false) { + MachineLocation loc; + long int offset; + + ReadLocation(&loc); + offset = loc.u.gmtDelta & 0x00ffffff; + if (offset & 0x00800000) { + offset = offset | 0xff000000; + } + if (ReadDateTime(&baseSeconds) != noErr) { + /* + * This should never happen! + */ + return; + } + /* + * Remove the local offset that ReadDateTime() adds. + */ + baseSeconds -= offset; + Microseconds(µOffset); + initalized = true; + } + + Microseconds(µ); + +#ifndef NO_LONG_LONG + microPtr = (long long *) µ + *microPtr -= *((long long *) µOffset); + timePtr->sec = baseSeconds + (*microPtr / 1000000); + timePtr->usec = *microPtr % 1000000; +#else + SubtractUnsignedWide(µ, µOffset, µ); + + /* + * This lovely computation is equal to: base + (micro / 1000000) + * For the .hi part the ratio of 0x100000000 / 1000000 has been + * reduced to avoid overflow. This computation certainly has + * problems as the .hi part gets large. However, your application + * would have to run for a long time to make that happen. + */ + + timePtr->sec = baseSeconds + (micro.lo / 1000000) + + (long) (micro.hi * ((double) 33554432.0 / 15625.0)); + timePtr->usec = micro.lo % 1000000; +#endif +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetDate -- + * + * Converts raw seconds to a struct tm data structure. The + * returned time will be for Greenwich Mean Time if the useGMT flag + * is set. Otherwise, the returned time will be for the local + * time zone. This function is meant to be used as a replacement + * for localtime and gmtime which is broken on most ANSI libs + * on the Macintosh. + * + * Results: + * None. + * + * Side effects: + * The passed in struct tm data structure is modified. + * + *---------------------------------------------------------------------- + */ + +struct tm * +TclpGetDate( + const time_t *tp, /* Time struct to fill. */ + int useGMT) /* True if date should reflect GNT time. */ +{ + DateTimeRec dtr; + MachineLocation loc; + long int offset; + static struct tm statictime; + static const short monthday[12] = + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + ReadLocation(&loc); + + if (useGMT) { + SecondsToDate(*tp, &dtr); + } else { + offset = loc.u.gmtDelta & 0x00ffffff; + if (offset & 0x00700000) { + offset |= 0xff000000; + } + + SecondsToDate(*tp + offset, &dtr); + } + + statictime.tm_sec = dtr.second; + statictime.tm_min = dtr.minute; + statictime.tm_hour = dtr.hour; + statictime.tm_mday = dtr.day; + statictime.tm_mon = dtr.month - 1; + statictime.tm_year = dtr.year - 1900; + statictime.tm_wday = dtr.dayOfWeek - 1; + statictime.tm_yday = monthday[statictime.tm_mon] + + statictime.tm_mday - 1; + if (1 < statictime.tm_mon && !(statictime.tm_year & 3)) { + ++statictime.tm_yday; + } + statictime.tm_isdst = loc.u.dlsDelta; + return(&statictime); +} + +#ifdef NO_LONG_LONG +/* + *---------------------------------------------------------------------- + * + * SubtractUnsignedWide -- + * + * Subtracts one UnsignedWide value from another. + * + * Results: + * The subtracted value. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +SubtractUnsignedWide( + UnsignedWide *x, /* Ptr to wide int. */ + UnsignedWide *y, /* Ptr to wide int. */ + UnsignedWide *result) /* Ptr to result. */ +{ + result->hi = x->hi - y->hi; + if (x->lo < y->lo) { + result->hi--; + } + result->lo = x->lo - y->lo; +} +#endif diff --git a/tcl/mac/tclMacUnix.c b/tcl/mac/tclMacUnix.c new file mode 100644 index 00000000000..9f5f42aa94c --- /dev/null +++ b/tcl/mac/tclMacUnix.c @@ -0,0 +1,464 @@ +/* + * tclMacUnix.c -- + * + * This file contains routines to implement several features + * available to the Unix implementation, but that require + * extra work to do on a Macintosh. These include routines + * Unix Tcl normally hands off to the Unix OS. + * + * Copyright (c) 1993-1994 Lockheed Missle & Space Company, AI Center + * Copyright (c) 1994-1996 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include <Files.h> +#include <Strings.h> +#include <TextUtils.h> +#include <Finder.h> +#include <FSpCompat.h> +#include <Aliases.h> +#include <Errors.h> + +#include "tclInt.h" +#include "tclMacInt.h" + +/* + * The following two Includes are from the More Files package + */ +#include "FileCopy.h" +#include "MoreFiles.h" +#include "MoreFilesExtras.h" + +/* + * The following may not be defined in some versions of + * MPW header files. + */ +#ifndef kIsInvisible +#define kIsInvisible 0x4000 +#endif +#ifndef kIsAlias +#define kIsAlias 0x8000 +#endif + +/* + * Missing error codes + */ +#define usageErr 500 +#define noSourceErr 501 +#define isDirErr 502 + +/* + * Static functions in this file. + */ + +static int GlobArgs _ANSI_ARGS_((Tcl_Interp *interp, + int *argc, char ***argv)); + +/* + *---------------------------------------------------------------------- + * + * GlobArgs -- + * + * The following function was taken from Peter Keleher's Alpha + * Editor. *argc should only count the end arguments that should + * be globed. argv should be incremented to point to the first + * arg to be globed. + * + * Results: + * Returns 'true' if it worked & memory was allocated, else 'false'. + * + * Side effects: + * argv will be alloced, the call will need to release the memory + * + *---------------------------------------------------------------------- + */ + +static int +GlobArgs( + Tcl_Interp *interp, /* Tcl interpreter. */ + int *argc, /* Number of arguments. */ + char ***argv) /* Argument strings. */ +{ + int res, len; + char *list; + + /* + * Places the globbed args all into 'interp->result' as a list. + */ + res = Tcl_GlobCmd(NULL, interp, *argc + 1, *argv - 1); + if (res != TCL_OK) { + return false; + } + len = strlen(interp->result); + list = (char *) ckalloc(len + 1); + strcpy(list, interp->result); + Tcl_ResetResult(interp); + + res = Tcl_SplitList(interp, list, argc, argv); + ckfree((char *) list); + if (res != TCL_OK) { + return false; + } + return true; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_EchoCmd -- + * + * Implements the TCL echo command: + * echo ?str ...? + * + * Results: + * Always returns TCL_OK. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_EchoCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int argc, /* Number of arguments. */ + char **argv) /* Argument strings. */ +{ + Tcl_Channel chan; + int mode, result, i; + + chan = Tcl_GetChannel(interp, "stdout", &mode); + if (chan == (Tcl_Channel) NULL) { + return TCL_ERROR; + } + for (i = 1; i < argc; i++) { + result = Tcl_Write(chan, argv[i], -1); + if (result < 0) { + Tcl_AppendResult(interp, "echo: ", Tcl_GetChannelName(chan), + ": ", Tcl_PosixError(interp), (char *) NULL); + return TCL_ERROR; + } + if (i < (argc - 1)) { + Tcl_Write(chan, " ", -1); + } + } + Tcl_Write(chan, "\n", -1); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_LsCmd -- + * + * This procedure is invoked to process the "ls" Tcl command. + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ +int +Tcl_LsCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int argc, /* Number of arguments. */ + char **argv) /* Argument strings. */ +{ +#define STRING_LENGTH 80 +#define CR '\n' + int i, j; + int fieldLength, len = 0, maxLen = 0, perLine; + char **origArgv = argv; + OSErr err; + CInfoPBRec paramBlock; + HFileInfo *hpb = (HFileInfo *)¶mBlock; + DirInfo *dpb = (DirInfo *)¶mBlock; + char theFile[256]; + char theLine[STRING_LENGTH + 2]; + int fFlag = false, pFlag = false, aFlag = false, lFlag = false, + cFlag = false, hFlag = false; + + /* + * Process command flags. End if argument doesn't start + * with a dash or is a dash by itself. The remaining arguments + * should be files. + */ + for (i = 1; i < argc; i++) { + if (argv[i][0] != '-') { + break; + } + + if (!strcmp(argv[i], "-")) { + i++; + break; + } + + for (j = 1 ; argv[i][j] ; ++j) { + switch(argv[i][j]) { + case 'a': + case 'A': + aFlag = true; + break; + case '1': + cFlag = false; + break; + case 'C': + cFlag = true; + break; + case 'F': + fFlag = true; + break; + case 'H': + hFlag = true; + break; + case 'p': + pFlag = true; + break; + case 'l': + pFlag = false; + lFlag = true; + break; + default: + Tcl_AppendResult(interp, "error - unknown flag ", + "usage: ls -apCFHl1 ?files? ", NULL); + return TCL_ERROR; + } + } + } + + argv += i; + argc -= i; + + /* + * No file specifications means we search for all files. + * Glob will be doing most of the work. + */ + if (!argc) { + argc = 1; + argv = origArgv; + strcpy(argv[0], "*"); + } + + if (!GlobArgs(interp, &argc, &argv)) { + Tcl_ResetResult(interp); + return TCL_ERROR; + } + + /* + * There are two major methods for listing files: the long + * method and the normal method. + */ + if (lFlag) { + char creator[5], type[5], time[16], date[16]; + char lineTag; + long size; + unsigned short flags; + + /* + * Print the header for long listing. + */ + if (hFlag) { + sprintf(theLine, "T %7s %8s %8s %4s %4s %6s %s", + "Size", "ModTime", "ModDate", + "CRTR", "TYPE", "Flags", "Name"); + Tcl_AppendResult(interp, theLine, "\n", NULL); + Tcl_AppendResult(interp, + "-------------------------------------------------------------\n", + NULL); + } + + for (i = 0; i < argc; i++) { + strcpy(theFile, argv[i]); + + c2pstr(theFile); + hpb->ioCompletion = NULL; + hpb->ioVRefNum = 0; + hpb->ioFDirIndex = 0; + hpb->ioNamePtr = (StringPtr) theFile; + hpb->ioDirID = 0L; + err = PBGetCatInfoSync(¶mBlock); + p2cstr((StringPtr) theFile); + + if (hpb->ioFlAttrib & 16) { + /* + * For directories use zero as the size, use no Creator + * type, and use 'DIR ' as the file type. + */ + if ((aFlag == false) && (dpb->ioDrUsrWds.frFlags & 0x1000)) { + continue; + } + lineTag = 'D'; + size = 0; + IUTimeString(dpb->ioDrMdDat, false, (unsigned char *)time); + p2cstr((StringPtr)time); + IUDateString(dpb->ioDrMdDat, shortDate, (unsigned char *)date); + p2cstr((StringPtr)date); + strcpy(creator, " "); + strcpy(type, "DIR "); + flags = dpb->ioDrUsrWds.frFlags; + if (fFlag || pFlag) { + strcat(theFile, ":"); + } + } else { + /* + * All information for files should be printed. This + * includes size, modtime, moddate, creator type, file + * type, flags, anf file name. + */ + if ((aFlag == false) && + (hpb->ioFlFndrInfo.fdFlags & kIsInvisible)) { + continue; + } + lineTag = 'F'; + size = hpb->ioFlLgLen + hpb->ioFlRLgLen; + IUTimeString(hpb->ioFlMdDat, false, (unsigned char *)time); + p2cstr((StringPtr)time); + IUDateString(hpb->ioFlMdDat, shortDate, (unsigned char *)date); + p2cstr((StringPtr)date); + strncpy(creator, (char *) &hpb->ioFlFndrInfo.fdCreator, 4); + creator[4] = 0; + strncpy(type, (char *) &hpb->ioFlFndrInfo.fdType, 4); + type[4] = 0; + flags = hpb->ioFlFndrInfo.fdFlags; + if (fFlag) { + if (hpb->ioFlFndrInfo.fdFlags & kIsAlias) { + strcat(theFile, "@"); + } else if (hpb->ioFlFndrInfo.fdType == 'APPL') { + strcat(theFile, "*"); + } + } + } + + sprintf(theLine, "%c %7ld %8s %8s %-4.4s %-4.4s 0x%4.4X %s", + lineTag, size, time, date, creator, type, flags, theFile); + + Tcl_AppendResult(interp, theLine, "\n", NULL); + + } + + if ((interp->result != NULL) && (*(interp->result) != '\0')) { + int slen = strlen(interp->result); + if (interp->result[slen - 1] == '\n') { + interp->result[slen - 1] = '\0'; + } + } + } else { + /* + * Not in long format. We only print files names. If the + * -C flag is set we need to print in multiple coloumns. + */ + int argCount, linePos; + Boolean needNewLine = false; + + /* + * Fiend the field length: the length each string printed + * to the terminal will be. + */ + if (!cFlag) { + perLine = 1; + fieldLength = STRING_LENGTH; + } else { + for (i = 0; i < argc; i++) { + len = strlen(argv[i]); + if (len > maxLen) { + maxLen = len; + } + } + fieldLength = maxLen + 3; + perLine = STRING_LENGTH / fieldLength; + } + + argCount = 0; + linePos = 0; + memset(theLine, ' ', STRING_LENGTH); + while (argCount < argc) { + strcpy(theFile, argv[argCount]); + + c2pstr(theFile); + hpb->ioCompletion = NULL; + hpb->ioVRefNum = 0; + hpb->ioFDirIndex = 0; + hpb->ioNamePtr = (StringPtr) theFile; + hpb->ioDirID = 0L; + err = PBGetCatInfoSync(¶mBlock); + p2cstr((StringPtr) theFile); + + if (hpb->ioFlAttrib & 16) { + /* + * Directory. If -a show hidden files. If -f or -p + * denote that this is a directory. + */ + if ((aFlag == false) && (dpb->ioDrUsrWds.frFlags & 0x1000)) { + argCount++; + continue; + } + if (fFlag || pFlag) { + strcat(theFile, ":"); + } + } else { + /* + * File: If -a show hidden files, if -f show links + * (aliases) and executables (APPLs). + */ + if ((aFlag == false) && + (hpb->ioFlFndrInfo.fdFlags & kIsInvisible)) { + argCount++; + continue; + } + if (fFlag) { + if (hpb->ioFlFndrInfo.fdFlags & kIsAlias) { + strcat(theFile, "@"); + } else if (hpb->ioFlFndrInfo.fdType == 'APPL') { + strcat(theFile, "*"); + } + } + } + + /* + * Print the item, taking into account multi- + * coloum output. + */ + strncpy(theLine + (linePos * fieldLength), theFile, + strlen(theFile)); + linePos++; + + if (linePos == perLine) { + theLine[STRING_LENGTH] = '\0'; + if (needNewLine) { + Tcl_AppendResult(interp, "\n", theLine, NULL); + } else { + Tcl_AppendResult(interp, theLine, NULL); + needNewLine = true; + } + linePos = 0; + memset(theLine, ' ', STRING_LENGTH); + } + + argCount++; + } + + if (linePos != 0) { + theLine[STRING_LENGTH] = '\0'; + if (needNewLine) { + Tcl_AppendResult(interp, "\n", theLine, NULL); + } else { + Tcl_AppendResult(interp, theLine, NULL); + } + } + } + + ckfree((char *) argv); + + return TCL_OK; +} diff --git a/tcl/mac/tclMacUtil.c b/tcl/mac/tclMacUtil.c new file mode 100644 index 00000000000..7d7867abe40 --- /dev/null +++ b/tcl/mac/tclMacUtil.c @@ -0,0 +1,441 @@ +/* + * tclMacUtil.c -- + * + * This contains utility functions used to help with + * implementing Macintosh specific portions of the Tcl port. + * + * Copyright (c) 1993-1994 Lockheed Missle & Space Company, AI Center + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id$ + */ + +#include "tcl.h" +#include "tclInt.h" +#include "tclMacInt.h" +#include "tclMath.h" +#include "tclMacPort.h" + +#include <Aliases.h> +#include <Errors.h> +#include <Files.h> +#include <Folders.h> +#include <FSpCompat.h> +#include <Strings.h> +#include <TextUtils.h> +#include <MoreFilesExtras.h> + +/* + * The following two Includes are from the More Files package. + */ +#include <FileCopy.h> +#include <MoreFiles.h> + +/* + *---------------------------------------------------------------------- + * + * hypotd -- + * + * The standard math function hypot is not supported by Think C. + * It is included here so everything works. It is supported by + * CodeWarrior Pro 1, but the 68K version does not support doubles. + * So we hack it in. + * + * Results: + * Result of computation. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +#if defined(THINK_C) || defined(__MWERKS__) +double hypotd(double x, double y); + +double +hypotd( + double x, /* X value */ + double y) /* Y value */ +{ + double sum; + + sum = x*x + y*y; + return sqrt(sum); +} +#endif + +/* + *---------------------------------------------------------------------- + * + * FSpGetDefaultDir -- + * + * This function gets the current default directory. + * + * Results: + * The provided FSSpec is changed to point to the "default" + * directory. The function returns what ever errors + * FSMakeFSSpecCompat may encounter. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +FSpGetDefaultDir( + FSSpecPtr dirSpec) /* On return the default directory. */ +{ + OSErr err; + short vRefNum = 0; + long int dirID = 0; + + err = HGetVol(NULL, &vRefNum, &dirID); + + if (err == noErr) { + err = FSMakeFSSpecCompat(vRefNum, dirID, (ConstStr255Param) NULL, + dirSpec); + } + + return err; +} + +/* + *---------------------------------------------------------------------- + * + * FSpSetDefaultDir -- + * + * This function sets the default directory to the directory + * pointed to by the provided FSSpec. + * + * Results: + * The function returns what ever errors HSetVol may encounter. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +FSpSetDefaultDir( + FSSpecPtr dirSpec) /* The new default directory. */ +{ + OSErr err; + + /* + * The following special case is needed to work around a bug + * in the Macintosh OS. (Acutally PC Exchange.) + */ + + if (dirSpec->parID == fsRtParID) { + err = HSetVol(NULL, dirSpec->vRefNum, fsRtDirID); + } else { + err = HSetVol(dirSpec->name, dirSpec->vRefNum, dirSpec->parID); + } + + return err; +} + +/* + *---------------------------------------------------------------------- + * + * FSpFindFolder -- + * + * This function is a version of the FindFolder function that + * returns the result as a FSSpec rather than a vRefNum and dirID. + * + * Results: + * Results will be simaler to that of the FindFolder function. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +OSErr +FSpFindFolder( + short vRefNum, /* Volume reference number. */ + OSType folderType, /* Folder type taken by FindFolder. */ + Boolean createFolder, /* Should we create it if non-existant. */ + FSSpec *spec) /* Pointer to resulting directory. */ +{ + short foundVRefNum; + long foundDirID; + OSErr err; + + err = FindFolder(vRefNum, folderType, createFolder, + &foundVRefNum, &foundDirID); + if (err != noErr) { + return err; + } + + err = FSMakeFSSpecCompat(foundVRefNum, foundDirID, "\p", spec); + return err; +} + +/* + *---------------------------------------------------------------------- + * + * FSpLocationFromPath -- + * + * This function obtains an FSSpec for a given macintosh path. + * Unlike the More Files function FSpLocationFromFullPath, this + * function will also accept partial paths and resolve any aliases + * along the path. + * + * Results: + * OSErr code. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +FSpLocationFromPath( + int length, /* Length of path. */ + CONST char *path, /* The path to convert. */ + FSSpecPtr fileSpecPtr) /* On return the spec for the path. */ +{ + Str255 fileName; + OSErr err; + short vRefNum; + long dirID; + int pos, cur; + Boolean isDirectory; + Boolean wasAlias; + + /* + * Check to see if this is a full path. If partial + * we assume that path starts with the current working + * directory. (Ie. volume & dir = 0) + */ + vRefNum = 0; + dirID = 0; + cur = 0; + if (length == 0) { + return fnfErr; + } + if (path[cur] == ':') { + cur++; + if (cur >= length) { + /* + * If path = ":", just return current directory. + */ + FSMakeFSSpecCompat(0, 0, NULL, fileSpecPtr); + return noErr; + } + } else { + while (path[cur] != ':' && cur < length) { + cur++; + } + if (cur > 255) { + return bdNamErr; + } + if (cur < length) { + /* + * This is a full path + */ + cur++; + strncpy((char *) fileName + 1, path, cur); + fileName[0] = cur; + err = FSMakeFSSpecCompat(0, 0, fileName, fileSpecPtr); + if (err != noErr) return err; + FSpGetDirectoryID(fileSpecPtr, &dirID, &isDirectory); + vRefNum = fileSpecPtr->vRefNum; + } else { + cur = 0; + } + } + + isDirectory = 1; + while (cur < length) { + if (!isDirectory) { + return dirNFErr; + } + pos = cur; + while (path[pos] != ':' && pos < length) { + pos++; + } + if (pos == cur) { + /* Move up one dir */ + /* cur++; */ + strcpy((char *) fileName + 1, "::"); + fileName[0] = 2; + } else if (pos - cur > 255) { + return bdNamErr; + } else { + strncpy((char *) fileName + 1, &path[cur], pos - cur); + fileName[0] = pos - cur; + } + err = FSMakeFSSpecCompat(vRefNum, dirID, fileName, fileSpecPtr); + if (err != noErr) return err; + err = ResolveAliasFile(fileSpecPtr, true, &isDirectory, &wasAlias); + if (err != noErr) return err; + FSpGetDirectoryID(fileSpecPtr, &dirID, &isDirectory); + vRefNum = fileSpecPtr->vRefNum; + cur = pos; + if (path[cur] == ':') { + cur++; + } + } + + return noErr; +} + +/* + *---------------------------------------------------------------------- + * + * FSpPathFromLocation -- + * + * This function obtains a full path name for a given macintosh + * FSSpec. Unlike the More Files function FSpGetFullPath, this + * function will return a C string in the Handle. It also will + * create paths for FSSpec that do not yet exist. + * + * Results: + * OSErr code. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +OSErr +FSpPathFromLocation( + FSSpec *spec, /* The location we want a path for. */ + int *length, /* Length of the resulting path. */ + Handle *fullPath) /* Handle to path. */ +{ + OSErr err; + FSSpec tempSpec; + CInfoPBRec pb; + + *fullPath = NULL; + + /* + * Make a copy of the input FSSpec that can be modified. + */ + BlockMoveData(spec, &tempSpec, sizeof(FSSpec)); + + if (tempSpec.parID == fsRtParID) { + /* + * The object is a volume. Add a colon to make it a full + * pathname. Allocate a handle for it and we are done. + */ + tempSpec.name[0] += 2; + tempSpec.name[tempSpec.name[0] - 1] = ':'; + tempSpec.name[tempSpec.name[0]] = '\0'; + + err = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]); + } else { + /* + * The object isn't a volume. Is the object a file or a directory? + */ + pb.dirInfo.ioNamePtr = tempSpec.name; + pb.dirInfo.ioVRefNum = tempSpec.vRefNum; + pb.dirInfo.ioDrDirID = tempSpec.parID; + pb.dirInfo.ioFDirIndex = 0; + err = PBGetCatInfoSync(&pb); + + if ((err == noErr) || (err == fnfErr)) { + /* + * If the file doesn't currently exist we start over. If the + * directory exists everything will work just fine. Otherwise we + * will just fail later. If the object is a directory, append a + * colon so full pathname ends with colon. + */ + if (err == fnfErr) { + BlockMoveData(spec, &tempSpec, sizeof(FSSpec)); + } else if ( (pb.hFileInfo.ioFlAttrib & ioDirMask) != 0 ) { + tempSpec.name[0] += 1; + tempSpec.name[tempSpec.name[0]] = ':'; + } + + /* + * Create a new Handle for the object - make it a C string. + */ + tempSpec.name[0] += 1; + tempSpec.name[tempSpec.name[0]] = '\0'; + err = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]); + if (err == noErr) { + /* + * Get the ancestor directory names - loop until we have an + * error or find the root directory. + */ + pb.dirInfo.ioNamePtr = tempSpec.name; + pb.dirInfo.ioVRefNum = tempSpec.vRefNum; + pb.dirInfo.ioDrParID = tempSpec.parID; + do { + pb.dirInfo.ioFDirIndex = -1; + pb.dirInfo.ioDrDirID = pb.dirInfo.ioDrParID; + err = PBGetCatInfoSync(&pb); + if (err == noErr) { + /* + * Append colon to directory name and add + * directory name to beginning of fullPath. + */ + ++tempSpec.name[0]; + tempSpec.name[tempSpec.name[0]] = ':'; + + (void) Munger(*fullPath, 0, NULL, 0, &tempSpec.name[1], + tempSpec.name[0]); + err = MemError(); + } + } while ( (err == noErr) && + (pb.dirInfo.ioDrDirID != fsRtDirID) ); + } + } + } + + /* + * On error Dispose the handle, set it to NULL & return the err. + * Otherwise, set the length & return. + */ + if (err == noErr) { + *length = GetHandleSize(*fullPath) - 1; + } else { + if ( *fullPath != NULL ) { + DisposeHandle(*fullPath); + } + *fullPath = NULL; + *length = 0; + } + + return err; +} + +/* + *---------------------------------------------------------------------- + * + * GetGlobalMouse -- + * + * This procedure obtains the current mouse position in global + * coordinates. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +GetGlobalMouse( + Point *mouse) /* Mouse position. */ +{ + EventRecord event; + + OSEventAvail(0, &event); + *mouse = event.where; +} |