1 #include <string.h>
2 
3 #include "jimautoconf.h"
4 #include <jim-subcmd.h>
5 
6 #ifdef HAVE_UNISTD_H
7 #include <unistd.h>
8 #else
9 #define R_OK 4
10 #endif
11 
12 /* All packages have a fixed, dummy version */
13 static const char *package_version_1 = "1.0";
14 
15 /* -----------------------------------------------------------------------------
16  * Packages handling
17  * ---------------------------------------------------------------------------*/
18 
Jim_PackageProvide(Jim_Interp * interp,const char * name,const char * ver,int flags)19 int Jim_PackageProvide(Jim_Interp *interp, const char *name, const char *ver, int flags)
20 {
21     /* If the package was already provided returns an error. */
22     Jim_HashEntry *he = Jim_FindHashEntry(&interp->packages, name);
23 
24     /* An empty result means the automatic entry. This can be replaced */
25     if (he && *(const char *)he->u.val) {
26         if (flags & JIM_ERRMSG) {
27             Jim_SetResultFormatted(interp, "package \"%s\" was already provided", name);
28         }
29         return JIM_ERR;
30     }
31     Jim_ReplaceHashEntry(&interp->packages, name, (char *)ver);
32     return JIM_OK;
33 }
34 
35 /**
36  * Searches along a of paths for the given package.
37  *
38  * Returns the allocated path to the package file if found,
39  * or NULL if not found.
40  */
JimFindPackage(Jim_Interp * interp,Jim_Obj * prefixListObj,const char * pkgName)41 static char *JimFindPackage(Jim_Interp *interp, Jim_Obj *prefixListObj, const char *pkgName)
42 {
43     int i;
44     char *buf = Jim_Alloc(JIM_PATH_LEN);
45     int prefixc = Jim_ListLength(interp, prefixListObj);
46 
47     for (i = 0; i < prefixc; i++) {
48         Jim_Obj *prefixObjPtr = Jim_ListGetIndex(interp, prefixListObj, i);
49         const char *prefix = Jim_String(prefixObjPtr);
50 
51         /* Loadable modules are tried first */
52 #ifdef jim_ext_load
53         snprintf(buf, JIM_PATH_LEN, "%s/%s.so", prefix, pkgName);
54         if (access(buf, R_OK) == 0) {
55             return buf;
56         }
57 #endif
58         if (strcmp(prefix, ".") == 0) {
59             snprintf(buf, JIM_PATH_LEN, "%s.tcl", pkgName);
60         }
61         else {
62             snprintf(buf, JIM_PATH_LEN, "%s/%s.tcl", prefix, pkgName);
63         }
64 
65         if (access(buf, R_OK) == 0) {
66             return buf;
67         }
68     }
69     Jim_Free(buf);
70     return NULL;
71 }
72 
73 /* Search for a suitable package under every dir specified by JIM_LIBPATH,
74  * and load it if possible. If a suitable package was loaded with success
75  * JIM_OK is returned, otherwise JIM_ERR is returned. */
JimLoadPackage(Jim_Interp * interp,const char * name,int flags)76 static int JimLoadPackage(Jim_Interp *interp, const char *name, int flags)
77 {
78     int retCode = JIM_ERR;
79     Jim_Obj *libPathObjPtr = Jim_GetGlobalVariableStr(interp, JIM_LIBPATH, JIM_NONE);
80     if (libPathObjPtr) {
81         char *path;
82 
83         /* Scan every directory for the the first match */
84         path = JimFindPackage(interp, libPathObjPtr, name);
85         if (path) {
86             const char *p;
87 
88             /* Note: Even if the file fails to load, we consider the package loaded.
89              *       This prevents issues with recursion.
90              *       Use a dummy version of "" to signify this case.
91              */
92             Jim_PackageProvide(interp, name, "", 0);
93 
94             /* Try to load/source it */
95             p = strrchr(path, '.');
96 
97             if (p && strcmp(p, ".tcl") == 0) {
98                 Jim_IncrRefCount(libPathObjPtr);
99                 retCode = Jim_EvalFileGlobal(interp, path);
100                 Jim_DecrRefCount(interp, libPathObjPtr);
101             }
102 #ifdef jim_ext_load
103             else {
104                 retCode = Jim_LoadLibrary(interp, path);
105             }
106 #endif
107             if (retCode != JIM_OK) {
108                 /* Upon failure, remove the dummy entry */
109                 Jim_DeleteHashEntry(&interp->packages, name);
110             }
111             Jim_Free(path);
112         }
113 
114         return retCode;
115     }
116     return JIM_ERR;
117 }
118 
Jim_PackageRequire(Jim_Interp * interp,const char * name,int flags)119 int Jim_PackageRequire(Jim_Interp *interp, const char *name, int flags)
120 {
121     Jim_HashEntry *he;
122 
123     /* Start with an empty error string */
124     Jim_SetEmptyResult(interp);
125 
126     he = Jim_FindHashEntry(&interp->packages, name);
127     if (he == NULL) {
128         /* Try to load the package. */
129         int retcode = JimLoadPackage(interp, name, flags);
130         if (retcode != JIM_OK) {
131             if (flags & JIM_ERRMSG) {
132                 int len = Jim_Length(Jim_GetResult(interp));
133                 Jim_SetResultFormatted(interp, "%#s%sCan't load package %s",
134                     Jim_GetResult(interp), len ? "\n" : "", name);
135             }
136             return retcode;
137         }
138 
139         /* In case the package did not 'package provide' */
140         Jim_PackageProvide(interp, name, package_version_1, 0);
141 
142         /* Now it must exist */
143         he = Jim_FindHashEntry(&interp->packages, name);
144     }
145 
146     Jim_SetResultString(interp, he->u.val, -1);
147     return JIM_OK;
148 }
149 
150 /*
151  *----------------------------------------------------------------------
152  *
153  * package provide name ?version?
154  *
155  *      This procedure is invoked to declare that
156  *      a particular package is now present in an interpreter.
157  *      The package must not already be provided in the interpreter.
158  *
159  * Results:
160  *      Returns JIM_OK and sets results as "1.0" (the given version is ignored)
161  *
162  *----------------------------------------------------------------------
163  */
package_cmd_provide(Jim_Interp * interp,int argc,Jim_Obj * const * argv)164 static int package_cmd_provide(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
165 {
166     return Jim_PackageProvide(interp, Jim_String(argv[0]), package_version_1, JIM_ERRMSG);
167 }
168 
169 /*
170  *----------------------------------------------------------------------
171  *
172  * package require name ?version?
173  *
174  *      This procedure is load a given package.
175  *      Note that the version is ignored.
176  *
177  * Results:
178  *      Returns JIM_OK and sets the package version.
179  *
180  *----------------------------------------------------------------------
181  */
package_cmd_require(Jim_Interp * interp,int argc,Jim_Obj * const * argv)182 static int package_cmd_require(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
183 {
184     /* package require failing is important enough to add to the stack */
185     interp->addStackTrace++;
186 
187     return Jim_PackageRequire(interp, Jim_String(argv[0]), JIM_ERRMSG);
188 }
189 
190 /*
191  *----------------------------------------------------------------------
192  *
193  * package list
194  *
195  *      Returns a list of known packages
196  *
197  * Results:
198  *      Returns JIM_OK and sets a list of known packages.
199  *
200  *----------------------------------------------------------------------
201  */
package_cmd_list(Jim_Interp * interp,int argc,Jim_Obj * const * argv)202 static int package_cmd_list(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
203 {
204     Jim_HashTableIterator *htiter;
205     Jim_HashEntry *he;
206     Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0);
207 
208     htiter = Jim_GetHashTableIterator(&interp->packages);
209     while ((he = Jim_NextHashEntry(htiter)) != NULL) {
210         Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, he->key, -1));
211     }
212     Jim_FreeHashTableIterator(htiter);
213 
214     Jim_SetResult(interp, listObjPtr);
215 
216     return JIM_OK;
217 }
218 
219 static const jim_subcmd_type package_command_table[] = {
220     {
221         "provide",
222         "name ?version?",
223         package_cmd_provide,
224         1,
225         2,
226         /* Description: Indicates that the current script provides the given package */
227     },
228     {
229         "require",
230         "name ?version?",
231         package_cmd_require,
232         1,
233         2,
234         /* Description: Loads the given package by looking in standard places */
235     },
236     {
237         "list",
238         NULL,
239         package_cmd_list,
240         0,
241         0,
242         /* Description: Lists all known packages */
243     },
244     {
245         NULL
246     }
247 };
248 
Jim_packageInit(Jim_Interp * interp)249 int Jim_packageInit(Jim_Interp *interp)
250 {
251     Jim_CreateCommand(interp, "package", Jim_SubCmdProc, (void *)package_command_table, NULL);
252     return JIM_OK;
253 }
254