main
cpp 673 lines 17.4 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4
5 // Exit macros
6 #define VerExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
7 #define VerExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
8 #define VerExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
9 #define VerExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
10 #define VerExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
11 #define VerExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
12 #define VerExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_VERUTIL, p, x, e, s, __VA_ARGS__)
13 #define VerExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_VERUTIL, p, x, s, __VA_ARGS__)
14 #define VerExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_VERUTIL, p, x, e, s, __VA_ARGS__)
15 #define VerExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_VERUTIL, p, x, s, __VA_ARGS__)
16 #define VerExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_VERUTIL, e, x, s, __VA_ARGS__)
17
18 // constants
19 const DWORD GROW_RELEASE_LABELS = 3;
20
21 // Forward declarations.
22 static int CompareDword(
23 __in const DWORD& dw1,
24 __in const DWORD& dw2
25 );
26 static HRESULT CompareReleaseLabel(
27 __in const VERUTIL_VERSION_RELEASE_LABEL* p1,
28 __in LPCWSTR wzVersion1,
29 __in const VERUTIL_VERSION_RELEASE_LABEL* p2,
30 __in LPCWSTR wzVersion2,
31 __out int* pnResult
32 );
33 static HRESULT CompareVersionSubstring(
34 __in LPCWSTR wzString1,
35 __in int cchCount1,
36 __in LPCWSTR wzString2,
37 __in int cchCount2,
38 __out int* pnResult
39 );
40
41
42 DAPI_(HRESULT) VerCompareParsedVersions(
43 __in_opt VERUTIL_VERSION* pVersion1,
44 __in_opt VERUTIL_VERSION* pVersion2,
45 __out int* pnResult
46 )
47 {
48 HRESULT hr = S_OK;
49 int nResult = 0;
50 DWORD cMaxReleaseLabels = 0;
51 BOOL fCompareMetadata = FALSE;
52
53 if (pVersion1 && !pVersion1->sczVersion ||
54 pVersion2 && !pVersion2->sczVersion)
55 {
56 ExitFunction1(hr = E_INVALIDARG);
57 }
58
59 if (pVersion1 == pVersion2)
60 {
61 ExitFunction1(nResult = 0);
62 }
63 else if (pVersion1 && !pVersion2)
64 {
65 ExitFunction1(nResult = 1);
66 }
67 else if (!pVersion1 && pVersion2)
68 {
69 ExitFunction1(nResult = -1);
70 }
71
72 nResult = CompareDword(pVersion1->dwMajor, pVersion2->dwMajor);
73 if (0 != nResult)
74 {
75 ExitFunction();
76 }
77
78 nResult = CompareDword(pVersion1->dwMinor, pVersion2->dwMinor);
79 if (0 != nResult)
80 {
81 ExitFunction();
82 }
83
84 nResult = CompareDword(pVersion1->dwPatch, pVersion2->dwPatch);
85 if (0 != nResult)
86 {
87 ExitFunction();
88 }
89
90 nResult = CompareDword(pVersion1->dwRevision, pVersion2->dwRevision);
91 if (0 != nResult)
92 {
93 ExitFunction();
94 }
95
96 if (pVersion1->cReleaseLabels)
97 {
98 if (pVersion2->cReleaseLabels)
99 {
100 cMaxReleaseLabels = max(pVersion1->cReleaseLabels, pVersion2->cReleaseLabels);
101 }
102 else
103 {
104 ExitFunction1(nResult = -1);
105 }
106 }
107 else if (pVersion2->cReleaseLabels)
108 {
109 ExitFunction1(nResult = 1);
110 }
111
112 if (cMaxReleaseLabels)
113 {
114 for (DWORD i = 0; i < cMaxReleaseLabels; ++i)
115 {
116 VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel1 = pVersion1->cReleaseLabels > i ? pVersion1->rgReleaseLabels + i : NULL;
117 VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel2 = pVersion2->cReleaseLabels > i ? pVersion2->rgReleaseLabels + i : NULL;
118
119 hr = CompareReleaseLabel(pReleaseLabel1, pVersion1->sczVersion, pReleaseLabel2, pVersion2->sczVersion, &nResult);
120 if (FAILED(hr) || 0 != nResult)
121 {
122 ExitFunction();
123 }
124 }
125 }
126
127 if (pVersion1->fInvalid)
128 {
129 if (!pVersion2->fInvalid)
130 {
131 ExitFunction1(nResult = -1);
132 }
133 else
134 {
135 fCompareMetadata = TRUE;
136 }
137 }
138 else if (pVersion2->fInvalid)
139 {
140 ExitFunction1(nResult = 1);
141 }
142
143 if (fCompareMetadata)
144 {
145 hr = CompareVersionSubstring(pVersion1->sczVersion + pVersion1->cchMetadataOffset, -1, pVersion2->sczVersion + pVersion2->cchMetadataOffset, -1, &nResult);
146 }
147
148 LExit:
149 *pnResult = nResult;
150 return hr;
151 }
152
153 DAPI_(HRESULT) VerCompareStringVersions(
154 __in_z LPCWSTR wzVersion1,
155 __in_z LPCWSTR wzVersion2,
156 __in BOOL fStrict,
157 __out int* pnResult
158 )
159 {
160 HRESULT hr = S_OK;
161 VERUTIL_VERSION* pVersion1 = NULL;
162 VERUTIL_VERSION* pVersion2 = NULL;
163 int nResult = 0;
164
165 hr = VerParseVersion(wzVersion1, 0, fStrict, &pVersion1);
166 VerExitOnFailure(hr, "Failed to parse Verutil version '%ls'", wzVersion1);
167
168 hr = VerParseVersion(wzVersion2, 0, fStrict, &pVersion2);
169 VerExitOnFailure(hr, "Failed to parse Verutil version '%ls'", wzVersion2);
170
171 hr = VerCompareParsedVersions(pVersion1, pVersion2, &nResult);
172 VerExitOnFailure(hr, "Failed to compare parsed Verutil versions '%ls' and '%ls'.", wzVersion1, wzVersion2);
173
174 LExit:
175 *pnResult = nResult;
176
177 ReleaseVerutilVersion(pVersion1);
178 ReleaseVerutilVersion(pVersion2);
179
180 return hr;
181 }
182
183 DAPI_(HRESULT) VerCopyVersion(
184 __in VERUTIL_VERSION* pSource,
185 __out VERUTIL_VERSION** ppVersion
186 )
187 {
188 HRESULT hr = S_OK;
189 VERUTIL_VERSION* pCopy = NULL;
190
191 pCopy = reinterpret_cast<VERUTIL_VERSION*>(MemAlloc(sizeof(VERUTIL_VERSION), TRUE));
192 VerExitOnNull(pCopy, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version copy.");
193
194 hr = StrAllocString(&pCopy->sczVersion, pSource->sczVersion, 0);
195 VerExitOnFailure(hr, "Failed to copy Verutil version string '%ls'.", pSource->sczVersion);
196
197 pCopy->chPrefix = pSource->chPrefix;
198 pCopy->dwMajor = pSource->dwMajor;
199 pCopy->fHasMajor = pSource->fHasMajor;
200 pCopy->dwMinor = pSource->dwMinor;
201 pCopy->fHasMinor = pSource->fHasMinor;
202 pCopy->dwPatch = pSource->dwPatch;
203 pCopy->fHasPatch = pSource->fHasPatch;
204 pCopy->dwRevision = pSource->dwRevision;
205 pCopy->fHasRevision = pSource->fHasRevision;
206
207 if (pSource->cReleaseLabels)
208 {
209 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pCopy->rgReleaseLabels), 0, sizeof(VERUTIL_VERSION_RELEASE_LABEL), pSource->cReleaseLabels);
210 VerExitOnFailure(hr, "Failed to allocate memory for Verutil version release labels copies.");
211
212 pCopy->cReleaseLabels = pSource->cReleaseLabels;
213
214 for (DWORD i = 0; i < pCopy->cReleaseLabels; ++i)
215 {
216 VERUTIL_VERSION_RELEASE_LABEL* pSourceLabel = pSource->rgReleaseLabels + i;
217 VERUTIL_VERSION_RELEASE_LABEL* pCopyLabel = pCopy->rgReleaseLabels + i;
218
219 pCopyLabel->cchLabelOffset = pSourceLabel->cchLabelOffset;
220 pCopyLabel->cchLabel = pSourceLabel->cchLabel;
221 pCopyLabel->fNumeric = pSourceLabel->fNumeric;
222 pCopyLabel->dwValue = pSourceLabel->dwValue;
223 }
224 }
225
226 pCopy->cchMetadataOffset = pSource->cchMetadataOffset;
227 pCopy->fInvalid = pSource->fInvalid;
228
229 *ppVersion = pCopy;
230 pCopy = NULL;
231
232 LExit:
233 ReleaseVerutilVersion(pCopy);
234
235 return hr;
236 }
237
238 DAPI_(void) VerFreeVersion(
239 __in VERUTIL_VERSION* pVersion
240 )
241 {
242 if (pVersion)
243 {
244 ReleaseStr(pVersion->sczVersion);
245 ReleaseMem(pVersion->rgReleaseLabels);
246 ReleaseMem(pVersion);
247 }
248 }
249
250 DAPI_(HRESULT) VerParseVersion(
251 __in_z LPCWSTR wzVersion,
252 __in SIZE_T cchVersion,
253 __in BOOL fStrict,
254 __out VERUTIL_VERSION** ppVersion
255 )
256 {
257 HRESULT hr = S_OK;
258 VERUTIL_VERSION* pVersion = NULL;
259 LPCWSTR wzString = NULL;
260 LPCWSTR wzEnd = NULL;
261 LPCWSTR wzPartBegin = NULL;
262 LPCWSTR wzPartEnd = NULL;
263 BOOL fInvalid = FALSE;
264 BOOL fLastPart = FALSE;
265 BOOL fTrailingDot = FALSE;
266 BOOL fParsedVersionNumber = FALSE;
267 BOOL fExpectedReleaseLabels = FALSE;
268 DWORD iPart = 0;
269
270 if (!wzVersion || !ppVersion)
271 {
272 ExitFunction1(hr = E_INVALIDARG);
273 }
274
275 // Get string length if not provided.
276 if (!cchVersion)
277 {
278 hr = ::StringCchLengthW(wzVersion, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchVersion));
279 VerExitOnRootFailure(hr, "Failed to get length of version string: %ls", wzVersion);
280 }
281 else if (INT_MAX < cchVersion)
282 {
283 VerExitOnRootFailure(hr = E_INVALIDARG, "Version string is too long: %Iu", cchVersion);
284 }
285
286 pVersion = reinterpret_cast<VERUTIL_VERSION*>(MemAlloc(sizeof(VERUTIL_VERSION), TRUE));
287 VerExitOnNull(pVersion, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version '%ls'.", wzVersion);
288
289 wzString = wzVersion;
290
291 if (L'v' == *wzString || L'V' == *wzString)
292 {
293 pVersion->chPrefix = *wzString;
294 ++wzString;
295 --cchVersion;
296 }
297
298 wzPartBegin = wzPartEnd = wzString;
299
300 // Save end pointer.
301 wzEnd = wzString + cchVersion;
302
303 // Parse version number
304 while (wzPartBegin < wzEnd)
305 {
306 fTrailingDot = FALSE;
307
308 // Find end of part.
309 for (;;)
310 {
311 if (wzPartEnd >= wzEnd)
312 {
313 fLastPart = TRUE;
314 break;
315 }
316
317 switch (*wzPartEnd)
318 {
319 case L'0':
320 case L'1':
321 case L'2':
322 case L'3':
323 case L'4':
324 case L'5':
325 case L'6':
326 case L'7':
327 case L'8':
328 case L'9':
329 ++wzPartEnd;
330 continue;
331 case L'.':
332 fTrailingDot = TRUE;
333 break;
334 case L'-':
335 case L'+':
336 fLastPart = TRUE;
337 break;
338 default:
339 fInvalid = TRUE;
340 break;
341 }
342
343 break;
344 }
345
346 if (wzPartBegin == wzPartEnd)
347 {
348 fInvalid = TRUE;
349 }
350
351 if (fInvalid)
352 {
353 break;
354 }
355
356 DWORD cchPart = 0;
357 hr = ::PtrdiffTToDWord(wzPartEnd - wzPartBegin, &cchPart);
358 if (FAILED(hr))
359 {
360 fInvalid = TRUE;
361 break;
362 }
363
364 // Parse version part.
365 UINT uPart = 0;
366 hr = StrStringToUInt32(wzPartBegin, cchPart, &uPart);
367 if (FAILED(hr))
368 {
369 fInvalid = TRUE;
370 break;
371 }
372
373 switch (iPart)
374 {
375 case 0:
376 pVersion->dwMajor = uPart;
377 pVersion->fHasMajor = TRUE;
378 break;
379 case 1:
380 pVersion->dwMinor = uPart;
381 pVersion->fHasMinor = TRUE;
382 break;
383 case 2:
384 pVersion->dwPatch = uPart;
385 pVersion->fHasPatch = TRUE;
386 break;
387 case 3:
388 pVersion->dwRevision = uPart;
389 pVersion->fHasRevision = TRUE;
390 break;
391 }
392
393 if (fTrailingDot)
394 {
395 ++wzPartEnd;
396 }
397 wzPartBegin = wzPartEnd;
398 ++iPart;
399
400 if (4 <= iPart || fLastPart)
401 {
402 fParsedVersionNumber = TRUE;
403 break;
404 }
405 }
406
407 fInvalid |= !fParsedVersionNumber || fTrailingDot;
408
409 if (!fInvalid && wzPartBegin < wzEnd && *wzPartBegin == L'-')
410 {
411 wzPartBegin = wzPartEnd = wzPartBegin + 1;
412 fExpectedReleaseLabels = TRUE;
413 fLastPart = FALSE;
414 }
415
416 while (fExpectedReleaseLabels && wzPartBegin < wzEnd)
417 {
418 fTrailingDot = FALSE;
419
420 // Find end of part.
421 for (;;)
422 {
423 if (wzPartEnd >= wzEnd)
424 {
425 fLastPart = TRUE;
426 break;
427 }
428
429 if (*wzPartEnd >= L'0' && *wzPartEnd <= L'9' ||
430 *wzPartEnd >= L'A' && *wzPartEnd <= L'Z' ||
431 *wzPartEnd >= L'a' && *wzPartEnd <= L'z' ||
432 *wzPartEnd == L'-')
433 {
434 ++wzPartEnd;
435 continue;
436 }
437 else if (*wzPartEnd == L'+')
438 {
439 fLastPart = TRUE;
440 }
441 else if (*wzPartEnd == L'.')
442 {
443 fTrailingDot = TRUE;
444 }
445 else
446 {
447 fInvalid = TRUE;
448 }
449
450 break;
451 }
452
453 if (wzPartBegin == wzPartEnd)
454 {
455 fInvalid = TRUE;
456 }
457
458 if (fInvalid)
459 {
460 break;
461 }
462
463 int cchLabel = 0;
464 hr = ::PtrdiffTToInt32(wzPartEnd - wzPartBegin, &cchLabel);
465 if (FAILED(hr) || 0 > cchLabel)
466 {
467 fInvalid = TRUE;
468 break;
469 }
470
471 hr = MemEnsureArraySizeForNewItems(reinterpret_cast<LPVOID*>(&pVersion->rgReleaseLabels), pVersion->cReleaseLabels, 1, sizeof(VERUTIL_VERSION_RELEASE_LABEL), GROW_RELEASE_LABELS);
472 VerExitOnFailure(hr, "Failed to allocate memory for Verutil version release labels '%ls'", wzVersion);
473
474 VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel = pVersion->rgReleaseLabels + pVersion->cReleaseLabels;
475 ++pVersion->cReleaseLabels;
476
477 // Try to parse as number.
478 UINT uLabel = 0;
479 hr = StrStringToUInt32(wzPartBegin, cchLabel, &uLabel);
480 if (SUCCEEDED(hr))
481 {
482 pReleaseLabel->fNumeric = TRUE;
483 pReleaseLabel->dwValue = uLabel;
484 }
485
486 pReleaseLabel->cchLabelOffset = wzPartBegin - wzString;
487 pReleaseLabel->cchLabel = cchLabel;
488
489 if (fTrailingDot)
490 {
491 ++wzPartEnd;
492 }
493 wzPartBegin = wzPartEnd;
494
495 if (fLastPart)
496 {
497 break;
498 }
499 }
500
501 fInvalid |= fExpectedReleaseLabels && (!pVersion->cReleaseLabels || fTrailingDot);
502
503 if (!fInvalid && wzPartBegin < wzEnd)
504 {
505 if (*wzPartBegin == L'+')
506 {
507 wzPartBegin = wzPartEnd = wzPartBegin + 1;
508 }
509 else
510 {
511 fInvalid = TRUE;
512 }
513 }
514
515 if (fInvalid && fStrict)
516 {
517 ExitFunction1(hr = E_INVALIDARG);
518 }
519
520 pVersion->cchMetadataOffset = min(wzPartBegin, wzEnd) - wzString;
521 pVersion->fInvalid = fInvalid;
522
523 // If the whole string was invalid, then don't clip off the prefix.
524 if (!pVersion->cchMetadataOffset && pVersion->chPrefix)
525 {
526 pVersion->chPrefix = '\0';
527 wzString = wzVersion;
528 ++cchVersion;
529 }
530
531 hr = StrAllocString(&pVersion->sczVersion, wzString, cchVersion);
532 VerExitOnFailure(hr, "Failed to copy Verutil version string '%ls'.", wzVersion);
533
534 *ppVersion = pVersion;
535 pVersion = NULL;
536 hr = S_OK;
537
538 LExit:
539 ReleaseVerutilVersion(pVersion);
540
541 return hr;
542 }
543
544 DAPI_(HRESULT) VerVersionFromQword(
545 __in DWORD64 qwVersion,
546 __out VERUTIL_VERSION** ppVersion
547 )
548 {
549 HRESULT hr = S_OK;
550 VERUTIL_VERSION* pVersion = NULL;
551
552 pVersion = reinterpret_cast<VERUTIL_VERSION*>(MemAlloc(sizeof(VERUTIL_VERSION), TRUE));
553 VerExitOnNull(pVersion, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version from QWORD.");
554
555 pVersion->dwMajor = (WORD)(qwVersion >> 48 & 0xffff);
556 pVersion->dwMinor = (WORD)(qwVersion >> 32 & 0xffff);
557 pVersion->dwPatch = (WORD)(qwVersion >> 16 & 0xffff);
558 pVersion->dwRevision = (WORD)(qwVersion & 0xffff);
559
560 pVersion->fHasMajor = TRUE;
561 pVersion->fHasMinor = TRUE;
562 pVersion->fHasPatch = TRUE;
563 pVersion->fHasRevision = TRUE;
564
565 hr = StrAllocFormatted(&pVersion->sczVersion, L"%lu.%lu.%lu.%lu", pVersion->dwMajor, pVersion->dwMinor, pVersion->dwPatch, pVersion->dwRevision);
566 ExitOnFailure(hr, "Failed to allocate and format the version string.");
567
568 pVersion->cchMetadataOffset = lstrlenW(pVersion->sczVersion);
569
570 *ppVersion = pVersion;
571 pVersion = NULL;
572
573 LExit:
574 ReleaseVerutilVersion(pVersion);
575
576 return hr;
577 }
578
579
580 static int CompareDword(
581 __in const DWORD& dw1,
582 __in const DWORD& dw2
583 )
584 {
585 int nResult = 0;
586
587 if (dw1 > dw2)
588 {
589 nResult = 1;
590 }
591 else if (dw1 < dw2)
592 {
593 nResult = -1;
594 }
595
596 return nResult;
597 }
598
599 static HRESULT CompareReleaseLabel(
600 __in const VERUTIL_VERSION_RELEASE_LABEL* p1,
601 __in LPCWSTR wzVersion1,
602 __in const VERUTIL_VERSION_RELEASE_LABEL* p2,
603 __in LPCWSTR wzVersion2,
604 __out int* pnResult
605 )
606 {
607 HRESULT hr = S_OK;
608 int nResult = 0;
609
610 if (p1 == p2)
611 {
612 ExitFunction();
613 }
614 else if (p1 && !p2)
615 {
616 ExitFunction1(nResult = 1);
617 }
618 else if (!p1 && p2)
619 {
620 ExitFunction1(nResult = -1);
621 }
622
623 if (p1->fNumeric)
624 {
625 if (p2->fNumeric)
626 {
627 nResult = CompareDword(p1->dwValue, p2->dwValue);
628 }
629 else
630 {
631 nResult = -1;
632 }
633 }
634 else
635 {
636 if (p2->fNumeric)
637 {
638 nResult = 1;
639 }
640 else
641 {
642 hr = CompareVersionSubstring(wzVersion1 + p1->cchLabelOffset, p1->cchLabel, wzVersion2 + p2->cchLabelOffset, p2->cchLabel, &nResult);
643 }
644 }
645
646 LExit:
647 *pnResult = nResult;
648
649 return hr;
650 }
651
652 static HRESULT CompareVersionSubstring(
653 __in LPCWSTR wzString1,
654 __in int cchCount1,
655 __in LPCWSTR wzString2,
656 __in int cchCount2,
657 __out int* pnResult
658 )
659 {
660 HRESULT hr = S_OK;
661 int nResult = 0;
662
663 nResult = ::CompareStringOrdinal(wzString1, cchCount1, wzString2, cchCount2, TRUE);
664 if (!nResult)
665 {
666 VerExitOnLastError(hr, "Failed to compare version substrings");
667 }
668
669 LExit:
670 *pnResult = nResult - 2;
671
672 return hr;
673 }