1 /*
2 * tclPkg.c --
3 *
4 * This file implements package and version control for Tcl via
5 * the "package" command and a few C APIs.
6 *
7 * Copyright (c) 1996 Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * SCCS: @(#) tclPkg.c 1.7 96/10/12 17:06:01
13 */
14
15 #include "tclInt.h"
16
17 /*
18 * Each invocation of the "package ifneeded" command creates a structure
19 * of the following type, which is used to load the package into the
20 * interpreter if it is requested with a "package require" command.
21 */
22
23 typedef struct PkgAvail {
24 char *version; /* Version string; malloc'ed. */
25 char *script; /* Script to invoke to provide this version
26 * of the package. Malloc'ed and protected
27 * by Tcl_Preserve and Tcl_Release. */
28 struct PkgAvail *nextPtr; /* Next in list of available versions of
29 * the same package. */
30 } PkgAvail;
31
32 /*
33 * For each package that is known in any way to an interpreter, there
34 * is one record of the following type. These records are stored in
35 * the "packageTable" hash table in the interpreter, keyed by
36 * package name such as "Tk" (no version number).
37 */
38
39 typedef struct Package {
40 char *version; /* Version that has been supplied in this
41 * interpreter via "package provide"
42 * (malloc'ed). NULL means the package doesn't
43 * exist in this interpreter yet. */
44 PkgAvail *availPtr; /* First in list of all available versions
45 * of this package. */
46 } Package;
47
48 /*
49 * Prototypes for procedures defined in this file:
50 */
51
52 static int CheckVersion _ANSI_ARGS_((Tcl_Interp *interp,
53 char *string));
54 static int ComparePkgVersions _ANSI_ARGS_((char *v1, char *v2,
55 int *satPtr));
56 static Package * FindPackage _ANSI_ARGS_((Tcl_Interp *interp,
57 char *name));
58
59 /*
60 *----------------------------------------------------------------------
61 *
62 * Tcl_PkgProvide --
63 *
64 * This procedure is invoked to declare that a particular version
65 * of a particular package is now present in an interpreter. There
66 * must not be any other version of this package already
67 * provided in the interpreter.
68 *
69 * Results:
70 * Normally returns TCL_OK; if there is already another version
71 * of the package loaded then TCL_ERROR is returned and an error
72 * message is left in interp->result.
73 *
74 * Side effects:
75 * The interpreter remembers that this package is available,
76 * so that no other version of the package may be provided for
77 * the interpreter.
78 *
79 *----------------------------------------------------------------------
80 */
81
82 int
Tcl_PkgProvide(interp,name,version)83 Tcl_PkgProvide(interp, name, version)
84 Tcl_Interp *interp; /* Interpreter in which package is now
85 * available. */
86 char *name; /* Name of package. */
87 char *version; /* Version string for package. */
88 {
89 Package *pkgPtr;
90
91 pkgPtr = FindPackage(interp, name);
92 if (pkgPtr->version == NULL) {
93 pkgPtr->version = ckalloc((unsigned) (strlen(version) + 1));
94 strcpy(pkgPtr->version, version);
95 return TCL_OK;
96 }
97 if (ComparePkgVersions(pkgPtr->version, version, (int *) NULL) == 0) {
98 return TCL_OK;
99 }
100 Tcl_AppendResult(interp, "conflicting versions provided for package \"",
101 name, "\": ", pkgPtr->version, ", then ", version, (char *) NULL);
102 return TCL_ERROR;
103 }
104
105 /*
106 *----------------------------------------------------------------------
107 *
108 * Tcl_PkgRequire --
109 *
110 * This procedure is called by code that depends on a particular
111 * version of a particular package. If the package is not already
112 * provided in the interpreter, this procedure invokes a Tcl script
113 * to provide it. If the package is already provided, this
114 * procedure makes sure that the caller's needs don't conflict with
115 * the version that is present.
116 *
117 * Results:
118 * If successful, returns the version string for the currently
119 * provided version of the package, which may be different from
120 * the "version" argument. If the caller's requirements
121 * cannot be met (e.g. the version requested conflicts with
122 * a currently provided version, or the required version cannot
123 * be found, or the script to provide the required version
124 * generates an error), NULL is returned and an error
125 * message is left in interp->result.
126 *
127 * Side effects:
128 * The script from some previous "package ifneeded" command may
129 * be invoked to provide the package.
130 *
131 *----------------------------------------------------------------------
132 */
133
134 char *
Tcl_PkgRequire(interp,name,version,exact)135 Tcl_PkgRequire(interp, name, version, exact)
136 Tcl_Interp *interp; /* Interpreter in which package is now
137 * available. */
138 char *name; /* Name of desired package. */
139 char *version; /* Version string for desired version;
140 * NULL means use the latest version
141 * available. */
142 int exact; /* Non-zero means that only the particular
143 * version given is acceptable. Zero means
144 * use the latest compatible version. */
145 {
146 Package *pkgPtr;
147 PkgAvail *availPtr, *bestPtr;
148 char *script;
149 int code, satisfies, result, pass;
150 Tcl_DString command;
151
152 /*
153 * It can take up to three passes to find the package: one pass to
154 * run the "package unknown" script, one to run the "package ifneeded"
155 * script for a specific version, and a final pass to lookup the
156 * package loaded by the "package ifneeded" script.
157 */
158
159 for (pass = 1; ; pass++) {
160 pkgPtr = FindPackage(interp, name);
161 if (pkgPtr->version != NULL) {
162 break;
163 }
164
165 /*
166 * The package isn't yet present. Search the list of available
167 * versions and invoke the script for the best available version.
168 */
169
170 bestPtr = NULL;
171 for (availPtr = pkgPtr->availPtr; availPtr != NULL;
172 availPtr = availPtr->nextPtr) {
173 if ((bestPtr != NULL) && (ComparePkgVersions(availPtr->version,
174 bestPtr->version, (int *) NULL) <= 0)) {
175 continue;
176 }
177 if (version != NULL) {
178 result = ComparePkgVersions(availPtr->version, version,
179 &satisfies);
180 if ((result != 0) && exact) {
181 continue;
182 }
183 if (!satisfies) {
184 continue;
185 }
186 }
187 bestPtr = availPtr;
188 }
189 if (bestPtr != NULL) {
190 /*
191 * We found an ifneeded script for the package. Be careful while
192 * executing it: this could cause reentrancy, so (a) protect the
193 * script itself from deletion and (b) don't assume that bestPtr
194 * will still exist when the script completes.
195 */
196
197 script = bestPtr->script;
198 Tcl_Preserve((ClientData) script);
199 code = Tcl_GlobalEval(interp, script);
200 Tcl_Release((ClientData) script);
201 if (code != TCL_OK) {
202 if (code == TCL_ERROR) {
203 Tcl_AddErrorInfo(interp,
204 "\n (\"package ifneeded\" script)");
205 }
206 return NULL;
207 }
208 Tcl_ResetResult(interp);
209 pkgPtr = FindPackage(interp, name);
210 break;
211 }
212
213 /*
214 * Package not in the database. If there is a "package unknown"
215 * command, invoke it (but only on the first pass; after that,
216 * we should not get here in the first place).
217 */
218
219 if (pass > 1) {
220 break;
221 }
222 script = ((Interp *) interp)->packageUnknown;
223 if (script != NULL) {
224 Tcl_DStringInit(&command);
225 Tcl_DStringAppend(&command, script, -1);
226 Tcl_DStringAppendElement(&command, name);
227 Tcl_DStringAppend(&command, " ", 1);
228 Tcl_DStringAppend(&command, (version != NULL) ? version : "{}",
229 -1);
230 if (exact) {
231 Tcl_DStringAppend(&command, " -exact", 7);
232 }
233 code = Tcl_GlobalEval(interp, Tcl_DStringValue(&command));
234 Tcl_DStringFree(&command);
235 if (code != TCL_OK) {
236 if (code == TCL_ERROR) {
237 Tcl_AddErrorInfo(interp,
238 "\n (\"package unknown\" script)");
239 }
240 return NULL;
241 }
242 Tcl_ResetResult(interp);
243 }
244 }
245
246 if (pkgPtr->version == NULL) {
247 Tcl_AppendResult(interp, "can't find package ", name,
248 (char *) NULL);
249 if (version != NULL) {
250 Tcl_AppendResult(interp, " ", version, (char *) NULL);
251 }
252 return NULL;
253 }
254
255 /*
256 * At this point we now that the package is present. Make sure that the
257 * provided version meets the current requirement.
258 */
259
260 if (version == NULL) {
261 return pkgPtr->version;
262 }
263 result = ComparePkgVersions(pkgPtr->version, version, &satisfies);
264 if ((satisfies && !exact) || (result == 0)) {
265 return pkgPtr->version;
266 }
267 Tcl_AppendResult(interp, "version conflict for package \"",
268 name, "\": have ", pkgPtr->version, ", need ", version,
269 (char *) NULL);
270 return NULL;
271 }
272
273 /*
274 *----------------------------------------------------------------------
275 *
276 * Tcl_PackageCmd --
277 *
278 * This procedure is invoked to process the "package" Tcl command.
279 * See the user documentation for details on what it does.
280 *
281 * Results:
282 * A standard Tcl result.
283 *
284 * Side effects:
285 * See the user documentation.
286 *
287 *----------------------------------------------------------------------
288 */
289
290 /* ARGSUSED */
291 int
Tcl_PackageCmd(dummy,interp,argc,argv)292 Tcl_PackageCmd(dummy, interp, argc, argv)
293 ClientData dummy; /* Not used. */
294 Tcl_Interp *interp; /* Current interpreter. */
295 int argc; /* Number of arguments. */
296 char **argv; /* Argument strings. */
297 {
298 Interp *iPtr = (Interp *) interp;
299 size_t length;
300 int c, exact, i, satisfies;
301 PkgAvail *availPtr, *prevPtr;
302 Package *pkgPtr;
303 Tcl_HashEntry *hPtr;
304 Tcl_HashSearch search;
305 Tcl_HashTable *tablePtr;
306 char *version;
307
308 if (argc < 2) {
309 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
310 " option ?arg arg ...?\"", (char *) NULL);
311 return TCL_ERROR;
312 }
313 c = argv[1][0];
314 length = strlen(argv[1]);
315 if ((c == 'f') && (strncmp(argv[1], "forget", length) == 0)) {
316 for (i = 2; i < argc; i++) {
317 hPtr = Tcl_FindHashEntry(&iPtr->packageTable, argv[i]);
318 if (hPtr == NULL) {
319 return TCL_OK;
320 }
321 pkgPtr = (Package *) Tcl_GetHashValue(hPtr);
322 Tcl_DeleteHashEntry(hPtr);
323 if (pkgPtr->version != NULL) {
324 ckfree(pkgPtr->version);
325 }
326 while (pkgPtr->availPtr != NULL) {
327 availPtr = pkgPtr->availPtr;
328 pkgPtr->availPtr = availPtr->nextPtr;
329 ckfree(availPtr->version);
330 Tcl_EventuallyFree((ClientData)availPtr->script, TCL_DYNAMIC);
331 ckfree((char *) availPtr);
332 }
333 ckfree((char *) pkgPtr);
334 }
335 } else if ((c == 'i') && (strncmp(argv[1], "ifneeded", length) == 0)) {
336 if ((argc != 4) && (argc != 5)) {
337 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
338 " ifneeded package version ?script?\"", (char *) NULL);
339 return TCL_ERROR;
340 }
341 if (CheckVersion(interp, argv[3]) != TCL_OK) {
342 return TCL_ERROR;
343 }
344 if (argc == 4) {
345 hPtr = Tcl_FindHashEntry(&iPtr->packageTable, argv[2]);
346 if (hPtr == NULL) {
347 return TCL_OK;
348 }
349 pkgPtr = (Package *) Tcl_GetHashValue(hPtr);
350 } else {
351 pkgPtr = FindPackage(interp, argv[2]);
352 }
353 for (availPtr = pkgPtr->availPtr, prevPtr = NULL; availPtr != NULL;
354 prevPtr = availPtr, availPtr = availPtr->nextPtr) {
355 if (ComparePkgVersions(availPtr->version, argv[3], (int *) NULL)
356 == 0) {
357 if (argc == 4) {
358 interp->result = availPtr->script;
359 return TCL_OK;
360 }
361 Tcl_EventuallyFree((ClientData)availPtr->script, TCL_DYNAMIC);
362 break;
363 }
364 }
365 if (argc == 4) {
366 return TCL_OK;
367 }
368 if (availPtr == NULL) {
369 availPtr = (PkgAvail *) ckalloc(sizeof(PkgAvail));
370 availPtr->version = ckalloc((unsigned) (strlen(argv[3]) + 1));
371 strcpy(availPtr->version, argv[3]);
372 if (prevPtr == NULL) {
373 availPtr->nextPtr = pkgPtr->availPtr;
374 pkgPtr->availPtr = availPtr;
375 } else {
376 availPtr->nextPtr = prevPtr->nextPtr;
377 prevPtr->nextPtr = availPtr;
378 }
379 }
380 availPtr->script = ckalloc((unsigned) (strlen(argv[4]) + 1));
381 strcpy(availPtr->script, argv[4]);
382 } else if ((c == 'n') && (strncmp(argv[1], "names", length) == 0)) {
383 if (argc != 2) {
384 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
385 " names\"", (char *) NULL);
386 return TCL_ERROR;
387 }
388 tablePtr = &iPtr->packageTable;
389 for (hPtr = Tcl_FirstHashEntry(tablePtr, &search); hPtr != NULL;
390 hPtr = Tcl_NextHashEntry(&search)) {
391 pkgPtr = (Package *) Tcl_GetHashValue(hPtr);
392 if ((pkgPtr->version != NULL) || (pkgPtr->availPtr != NULL)) {
393 Tcl_AppendElement(interp, Tcl_GetHashKey(tablePtr, hPtr));
394 }
395 }
396 } else if ((c == 'p') && (strncmp(argv[1], "provide", length) == 0)) {
397 if ((argc != 3) && (argc != 4)) {
398 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
399 " provide package ?version?\"", (char *) NULL);
400 return TCL_ERROR;
401 }
402 if (argc == 3) {
403 hPtr = Tcl_FindHashEntry(&iPtr->packageTable, argv[2]);
404 if (hPtr != NULL) {
405 pkgPtr = (Package *) Tcl_GetHashValue(hPtr);
406 if (pkgPtr->version != NULL) {
407 interp->result = pkgPtr->version;
408 }
409 }
410 return TCL_OK;
411 }
412 if (CheckVersion(interp, argv[3]) != TCL_OK) {
413 return TCL_ERROR;
414 }
415 return Tcl_PkgProvide(interp, argv[2], argv[3]);
416 } else if ((c == 'r') && (strncmp(argv[1], "require", length) == 0)) {
417 if (argc < 3) {
418 requireSyntax:
419 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
420 " require ?-exact? package ?version?\"", (char *) NULL);
421 return TCL_ERROR;
422 }
423 if ((argv[2][0] == '-') && (strcmp(argv[2], "-exact") == 0)) {
424 exact = 1;
425 } else {
426 exact = 0;
427 }
428 version = NULL;
429 if (argc == (4+exact)) {
430 version = argv[3+exact];
431 if (CheckVersion(interp, version) != TCL_OK) {
432 return TCL_ERROR;
433 }
434 } else if ((argc != 3) || exact) {
435 goto requireSyntax;
436 }
437 version = Tcl_PkgRequire(interp, argv[2+exact], version, exact);
438 if (version == NULL) {
439 return TCL_ERROR;
440 }
441 interp->result = version;
442 } else if ((c == 'u') && (strncmp(argv[1], "unknown", length) == 0)) {
443 if (argc == 2) {
444 if (iPtr->packageUnknown != NULL) {
445 iPtr->result = iPtr->packageUnknown;
446 }
447 } else if (argc == 3) {
448 if (iPtr->packageUnknown != NULL) {
449 ckfree(iPtr->packageUnknown);
450 }
451 if (argv[2][0] == 0) {
452 iPtr->packageUnknown = NULL;
453 } else {
454 iPtr->packageUnknown = (char *) ckalloc((unsigned)
455 (strlen(argv[2]) + 1));
456 strcpy(iPtr->packageUnknown, argv[2]);
457 }
458 } else {
459 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
460 " unknown ?command?\"", (char *) NULL);
461 return TCL_ERROR;
462 }
463 } else if ((c == 'v') && (strncmp(argv[1], "vcompare", length) == 0)
464 && (length >= 2)) {
465 if (argc != 4) {
466 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
467 " vcompare version1 version2\"", (char *) NULL);
468 return TCL_ERROR;
469 }
470 if ((CheckVersion(interp, argv[2]) != TCL_OK)
471 || (CheckVersion(interp, argv[3]) != TCL_OK)) {
472 return TCL_ERROR;
473 }
474 sprintf(interp->result, "%d", ComparePkgVersions(argv[2], argv[3],
475 (int *) NULL));
476 } else if ((c == 'v') && (strncmp(argv[1], "versions", length) == 0)
477 && (length >= 2)) {
478 if (argc != 3) {
479 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
480 " versions package\"", (char *) NULL);
481 return TCL_ERROR;
482 }
483 hPtr = Tcl_FindHashEntry(&iPtr->packageTable, argv[2]);
484 if (hPtr != NULL) {
485 pkgPtr = (Package *) Tcl_GetHashValue(hPtr);
486 for (availPtr = pkgPtr->availPtr; availPtr != NULL;
487 availPtr = availPtr->nextPtr) {
488 Tcl_AppendElement(interp, availPtr->version);
489 }
490 }
491 } else if ((c == 'v') && (strncmp(argv[1], "vsatisfies", length) == 0)
492 && (length >= 2)) {
493 if (argc != 4) {
494 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
495 " vsatisfies version1 version2\"", (char *) NULL);
496 return TCL_ERROR;
497 }
498 if ((CheckVersion(interp, argv[2]) != TCL_OK)
499 || (CheckVersion(interp, argv[3]) != TCL_OK)) {
500 return TCL_ERROR;
501 }
502 ComparePkgVersions(argv[2], argv[3], &satisfies);
503 sprintf(interp->result, "%d", satisfies);
504 } else {
505 Tcl_AppendResult(interp, "bad option \"", argv[1],
506 "\": should be forget, ifneeded, names, ",
507 "provide, require, unknown, vcompare, ",
508 "versions, or vsatisfies", (char *) NULL);
509 return TCL_ERROR;
510 }
511 return TCL_OK;
512 }
513
514 /*
515 *----------------------------------------------------------------------
516 *
517 * FindPackage --
518 *
519 * This procedure finds the Package record for a particular package
520 * in a particular interpreter, creating a record if one doesn't
521 * already exist.
522 *
523 * Results:
524 * The return value is a pointer to the Package record for the
525 * package.
526 *
527 * Side effects:
528 * A new Package record may be created.
529 *
530 *----------------------------------------------------------------------
531 */
532
533 static Package *
FindPackage(interp,name)534 FindPackage(interp, name)
535 Tcl_Interp *interp; /* Interpreter to use for package lookup. */
536 char *name; /* Name of package to fine. */
537 {
538 Interp *iPtr = (Interp *) interp;
539 Tcl_HashEntry *hPtr;
540 int new;
541 Package *pkgPtr;
542
543 hPtr = Tcl_CreateHashEntry(&iPtr->packageTable, name, &new);
544 if (new) {
545 pkgPtr = (Package *) ckalloc(sizeof(Package));
546 pkgPtr->version = NULL;
547 pkgPtr->availPtr = NULL;
548 Tcl_SetHashValue(hPtr, pkgPtr);
549 } else {
550 pkgPtr = (Package *) Tcl_GetHashValue(hPtr);
551 }
552 return pkgPtr;
553 }
554
555 /*
556 *----------------------------------------------------------------------
557 *
558 * TclFreePackageInfo --
559 *
560 * This procedure is called during interpreter deletion to
561 * free all of the package-related information for the
562 * interpreter.
563 *
564 * Results:
565 * None.
566 *
567 * Side effects:
568 * Memory is freed.
569 *
570 *----------------------------------------------------------------------
571 */
572
573 void
TclFreePackageInfo(iPtr)574 TclFreePackageInfo(iPtr)
575 Interp *iPtr; /* Interpereter that is being deleted. */
576 {
577 Package *pkgPtr;
578 Tcl_HashSearch search;
579 Tcl_HashEntry *hPtr;
580 PkgAvail *availPtr;
581
582 for (hPtr = Tcl_FirstHashEntry(&iPtr->packageTable, &search);
583 hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
584 pkgPtr = (Package *) Tcl_GetHashValue(hPtr);
585 if (pkgPtr->version != NULL) {
586 ckfree(pkgPtr->version);
587 }
588 while (pkgPtr->availPtr != NULL) {
589 availPtr = pkgPtr->availPtr;
590 pkgPtr->availPtr = availPtr->nextPtr;
591 ckfree(availPtr->version);
592 Tcl_EventuallyFree((ClientData)availPtr->script, TCL_DYNAMIC);
593 ckfree((char *) availPtr);
594 }
595 ckfree((char *) pkgPtr);
596 }
597 Tcl_DeleteHashTable(&iPtr->packageTable);
598 if (iPtr->packageUnknown != NULL) {
599 ckfree(iPtr->packageUnknown);
600 }
601 }
602
603 /*
604 *----------------------------------------------------------------------
605 *
606 * CheckVersion --
607 *
608 * This procedure checks to see whether a version number has
609 * valid syntax.
610 *
611 * Results:
612 * If string is a properly formed version number the TCL_OK
613 * is returned. Otherwise TCL_ERROR is returned and an error
614 * message is left in interp->result.
615 *
616 * Side effects:
617 * None.
618 *
619 *----------------------------------------------------------------------
620 */
621
622 static int
CheckVersion(interp,string)623 CheckVersion(interp, string)
624 Tcl_Interp *interp; /* Used for error reporting. */
625 char *string; /* Supposedly a version number, which is
626 * groups of decimal digits separated
627 * by dots. */
628 {
629 char *p = string;
630
631 if (!isdigit(UCHAR(*p))) {
632 goto error;
633 }
634 for (p++; *p != 0; p++) {
635 if (!isdigit(UCHAR(*p)) && (*p != '.')) {
636 goto error;
637 }
638 }
639 if (p[-1] != '.') {
640 return TCL_OK;
641 }
642
643 error:
644 Tcl_AppendResult(interp, "expected version number but got \"",
645 string, "\"", (char *) NULL);
646 return TCL_ERROR;
647 }
648
649 /*
650 *----------------------------------------------------------------------
651 *
652 * ComparePkgVersions --
653 *
654 * This procedure compares two version numbers.
655 *
656 * Results:
657 * The return value is -1 if v1 is less than v2, 0 if the two
658 * version numbers are the same, and 1 if v1 is greater than v2.
659 * If *satPtr is non-NULL, the word it points to is filled in
660 * with 1 if v2 >= v1 and both numbers have the same major number
661 * or 0 otherwise.
662 *
663 * Side effects:
664 * None.
665 *
666 *----------------------------------------------------------------------
667 */
668
669 static int
ComparePkgVersions(v1,v2,satPtr)670 ComparePkgVersions(v1, v2, satPtr)
671 char *v1, *v2; /* Versions strings, of form 2.1.3 (any
672 * number of version numbers). */
673 int *satPtr; /* If non-null, the word pointed to is
674 * filled in with a 0/1 value. 1 means
675 * v1 "satisfies" v2: v1 is greater than
676 * or equal to v2 and both version numbers
677 * have the same major number. */
678 {
679 int thisIsMajor, n1, n2;
680
681 /*
682 * Each iteration of the following loop processes one number from
683 * each string, terminated by a ".". If those numbers don't match
684 * then the comparison is over; otherwise, we loop back for the
685 * next number.
686 */
687
688 thisIsMajor = 1;
689 while (1) {
690 /*
691 * Parse one decimal number from the front of each string.
692 */
693
694 n1 = n2 = 0;
695 while ((*v1 != 0) && (*v1 != '.')) {
696 n1 = 10*n1 + (*v1 - '0');
697 v1++;
698 }
699 while ((*v2 != 0) && (*v2 != '.')) {
700 n2 = 10*n2 + (*v2 - '0');
701 v2++;
702 }
703
704 /*
705 * Compare and go on to the next version number if the
706 * current numbers match.
707 */
708
709 if (n1 != n2) {
710 break;
711 }
712 if (*v1 != 0) {
713 v1++;
714 } else if (*v2 == 0) {
715 break;
716 }
717 if (*v2 != 0) {
718 v2++;
719 }
720 thisIsMajor = 0;
721 }
722 if (satPtr != NULL) {
723 *satPtr = (n1 == n2) || ((n1 > n2) && !thisIsMajor);
724 }
725 if (n1 > n2) {
726 return 1;
727 } else if (n1 == n2) {
728 return 0;
729 } else {
730 return -1;
731 }
732 }
733