/* * vercomp.c * * Copyright (c) 2010 The MacPorts Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright owner nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #if HAVE_CONFIG_H #include #endif #include "vercomp.h" #include #include /* * TODO: share this function between pextlib and cregistry. The version here is * slightly modified so as to take explicit string lengths. Since these * are available in Tcl it's an easy change and might be a tiny bit * faster; it's necessary for the application here. */ /** * EVR version comparison. Shamelessly copied from Pextlib, with some changes to * use string lengths instead of strlen by default. That's necessary to make it * work with sqlite3 collations. It should be shared with Pextlib, rather than * just copied though. * * @param [in] versionA first version string, i.e. "1.4.1" * @param [in] lengthA length of first version string, or -1 to use strlen * @param [in] versionB second version string, i.e. "1.4.2" * @param [in] lengthB length of second version string, or -1 to use strlen * @return -1 if A < B; 0 if A = B; 1 if A > B */ static int vercmp (const char *versionA, int lengthA, const char *versionB, int lengthB) { const char *endA, *endB; const char *ptrA, *ptrB; const char *eptrA, *eptrB; if (lengthA < 0) lengthA = (int)strlen(versionA); if (lengthB < 0) lengthB = (int)strlen(versionB); /* if versions equal, return zero */ if (lengthA == lengthB && !strncmp(versionA, versionB, (size_t)lengthA)) return 0; ptrA = versionA; ptrB = versionB; endA = versionA + lengthA; endB = versionB + lengthB; while (ptrA != endA && ptrB != endB) { /* skip all non-alphanumeric characters */ while (ptrA != endB && !isalnum(*ptrA)) ptrA++; while (ptrB != endB && !isalnum(*ptrB)) ptrB++; eptrA = ptrA; eptrB = ptrB; /* Somewhat arbitrary rules as per RPM's implementation. * This code could be more clever, but we're aiming * for clarity instead. */ /* If versionB's segment is not a digit segment, but * versionA's segment IS a digit segment, return 1. * (Added for redhat compatibility. See redhat bugzilla * #50977 for details) */ if (!isdigit(*ptrB)) { if (isdigit(*ptrA)) return 1; } /* Otherwise, if the segments are of different types, * return -1 */ if ((isdigit(*ptrA) && isalpha(*ptrB)) || (isalpha(*ptrA) && isdigit(*ptrB))) return -1; /* Find the first segment composed of entirely alphabetical * or numeric members */ if (isalpha(*ptrA)) { while (eptrA != endA && isalpha(*eptrA)) eptrA++; while (eptrB != endB && isalpha(*eptrB)) eptrB++; } else { int countA = 0, countB = 0; while (eptrA != endA && isdigit(*eptrA)) { countA++; eptrA++; } while (eptrB != endB && isdigit(*eptrB)) { countB++; eptrB++; } /* skip leading '0' characters */ while (ptrA != eptrA && *ptrA == '0') { ptrA++; countA--; } while (ptrB != eptrB && *ptrB == '0') { ptrB++; countB--; } /* If A is longer than B, return 1 */ if (countA > countB) return 1; /* If B is longer than A, return -1 */ if (countB > countA) return -1; } /* Compare strings lexicographically */ while (ptrA != eptrA && ptrB != eptrB && *ptrA == *ptrB) { ptrA++; ptrB++; } if (ptrA != eptrA && ptrB != eptrB) return *ptrA - *ptrB; ptrA = eptrA; ptrB = eptrB; } /* If both pointers are null, all alphanumeric * characters were identical and only separating * characters differed. According to RPM, these * version strings are equal */ if (ptrA == endA && ptrB == endB) return 0; /* If A has unchecked characters, return 1 * Otherwise, if B has remaining unchecked characters, * return -1 */ if (ptrA != endA) return 1; else return -1; } /** * VERSION collation for sqlite3. This function collates text according to * pextlib's vercmp function. This allows direct comparison and sorting of * version columns, such as port.version and port.revision. * * @param [in] userdata unused * @param [in] alen length of first string * @param [in] a first string * @param [in] blen length of second string * @param [in] b second string * @return -1 if a < b; 0 if a = b; 1 if a > b */ int sql_version(void* userdata UNUSED, int alen, const void* a, int blen, const void* b) { return vercmp((const char*)a, alen, (const char*)b, blen); }