1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <cf3.defs.h>
26 #include <vercmp.h>
27 #include <vercmp_internal.h>
28
29 #include <actuator.h>
30 #include <scope.h>
31 #include <expand.h>
32 #include <vars.h>
33 #include <pipes.h>
34 #include <misc_lib.h>
35 #include <eval_context.h>
36
InvertResult(VersionCmpResult result)37 static VersionCmpResult InvertResult(VersionCmpResult result)
38 {
39 if (result == VERCMP_ERROR)
40 {
41 return VERCMP_ERROR;
42 }
43 else
44 {
45 return !result;
46 }
47 }
48
AndResults(VersionCmpResult lhs,VersionCmpResult rhs)49 static VersionCmpResult AndResults(VersionCmpResult lhs, VersionCmpResult rhs)
50 {
51 if ((lhs == VERCMP_ERROR) || (rhs == VERCMP_ERROR))
52 {
53 return VERCMP_ERROR;
54 }
55 else
56 {
57 return ((VersionCmpResult) (lhs && rhs));
58 }
59 }
60
RunCmpCommand(EvalContext * ctx,const char * command,const char * v1,const char * v2,const Attributes * a,const Promise * pp,PromiseResult * result)61 static VersionCmpResult RunCmpCommand(EvalContext *ctx, const char *command, const char *v1, const char *v2, const Attributes *a,
62 const Promise *pp, PromiseResult *result)
63 {
64 Buffer *expanded_command = BufferNew();
65 {
66 VarRef *ref_v1 = VarRefParseFromScope("v1", PACKAGES_CONTEXT);
67 EvalContextVariablePut(ctx, ref_v1, v1, CF_DATA_TYPE_STRING, "source=promise");
68
69 VarRef *ref_v2 = VarRefParseFromScope("v2", PACKAGES_CONTEXT);
70 EvalContextVariablePut(ctx, ref_v2, v2, CF_DATA_TYPE_STRING, "source=promise");
71
72 ExpandScalar(ctx, NULL, PACKAGES_CONTEXT, command, expanded_command);
73
74 EvalContextVariableRemove(ctx, ref_v1);
75 VarRefDestroy(ref_v1);
76
77 EvalContextVariableRemove(ctx, ref_v2);
78 VarRefDestroy(ref_v2);
79 }
80
81 FILE *pfp = a->packages.package_commands_useshell ? cf_popen_sh(BufferData(expanded_command), "w") : cf_popen(BufferData(expanded_command), "w", true);
82
83 if (pfp == NULL)
84 {
85 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Can not start package version comparison command '%s'. (cf_popen: %s)",
86 BufferData(expanded_command), GetErrorStr());
87 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
88 BufferDestroy(expanded_command);
89 return VERCMP_ERROR;
90 }
91
92 Log(LOG_LEVEL_VERBOSE, "Executing '%s'", BufferData(expanded_command));
93
94 int retcode = cf_pclose(pfp);
95 Log(LOG_LEVEL_VERBOSE, "returned: %d", retcode);
96
97 if (retcode == -1)
98 {
99 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Error during package version comparison command execution '%s'. (cf_pclose: %s)",
100 BufferData(expanded_command), GetErrorStr());
101 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
102 BufferDestroy(expanded_command);
103 return VERCMP_ERROR;
104 }
105
106 BufferDestroy(expanded_command);
107
108 if (retcode == 0)
109 {
110 return VERCMP_MATCH;
111 }
112 else
113 {
114 return VERCMP_NO_MATCH;
115 }
116 }
117
CompareVersionsLess(EvalContext * ctx,const char * v1,const char * v2,const Attributes * a,const Promise * pp,PromiseResult * result)118 static VersionCmpResult CompareVersionsLess(EvalContext *ctx, const char *v1, const char *v2, const Attributes *a,
119 const Promise *pp, PromiseResult *result)
120 {
121 if (a->packages.package_version_less_command)
122 {
123 return RunCmpCommand(ctx, a->packages.package_version_less_command, v1, v2, a, pp, result);
124 }
125 else
126 {
127 return ComparePackageVersionsInternal(v1, v2, PACKAGE_VERSION_COMPARATOR_LT);
128 }
129 }
130
CompareVersionsEqual(EvalContext * ctx,const char * v1,const char * v2,const Attributes * a,const Promise * pp,PromiseResult * result)131 static VersionCmpResult CompareVersionsEqual(EvalContext *ctx, const char *v1, const char *v2, const Attributes *a,
132 const Promise *pp, PromiseResult *result)
133 {
134 if (a->packages.package_version_equal_command)
135 {
136 return RunCmpCommand(ctx, a->packages.package_version_equal_command, v1, v2, a, pp, result);
137 }
138 else if (a->packages.package_version_less_command)
139 {
140 /* emulate v1 == v2 by !(v1 < v2) && !(v2 < v1) */
141 return AndResults(InvertResult(CompareVersionsLess(ctx, v1, v2, a, pp, result)),
142 InvertResult(CompareVersionsLess(ctx, v2, v1, a, pp, result)));
143 }
144 else
145 {
146 /* Built-in fallback */
147 return ComparePackageVersionsInternal(v1, v2, PACKAGE_VERSION_COMPARATOR_EQ);
148 }
149 }
150
CompareVersions(EvalContext * ctx,const char * v1,const char * v2,const Attributes * a,const Promise * pp,PromiseResult * result)151 VersionCmpResult CompareVersions(EvalContext *ctx, const char *v1, const char *v2, const Attributes *a,
152 const Promise *pp, PromiseResult *result)
153 {
154 VersionCmpResult cmp_result;
155
156 switch (a->packages.package_select)
157 {
158 case PACKAGE_VERSION_COMPARATOR_EQ:
159 case PACKAGE_VERSION_COMPARATOR_NONE:
160 cmp_result = CompareVersionsEqual(ctx, v1, v2, a, pp, result);
161 break;
162 case PACKAGE_VERSION_COMPARATOR_NEQ:
163 cmp_result = InvertResult(CompareVersionsEqual(ctx, v1, v2, a, pp, result));
164 break;
165 case PACKAGE_VERSION_COMPARATOR_LT:
166 cmp_result = CompareVersionsLess(ctx, v1, v2, a, pp, result);
167 break;
168 case PACKAGE_VERSION_COMPARATOR_GT:
169 cmp_result = CompareVersionsLess(ctx, v2, v1, a, pp, result);
170 break;
171 case PACKAGE_VERSION_COMPARATOR_GE:
172 cmp_result = InvertResult(CompareVersionsLess(ctx, v1, v2, a, pp, result));
173 break;
174 case PACKAGE_VERSION_COMPARATOR_LE:
175 cmp_result = InvertResult(CompareVersionsLess(ctx, v2, v1, a, pp, result));
176 break;
177 default:
178 ProgrammingError("Unexpected comparison value: %d", a->packages.package_select);
179 break;
180 }
181
182 const char *text_result;
183 switch (cmp_result)
184 {
185 case VERCMP_NO_MATCH:
186 text_result = "no";
187 break;
188 case VERCMP_MATCH:
189 text_result = "yes";
190 break;
191 default:
192 text_result = "Incompatible version format. Can't decide";
193 break;
194 }
195
196 Log(LOG_LEVEL_VERBOSE, "CompareVersions: Checked whether package version %s %s %s: %s",
197 v1, PackageVersionComparatorToString(a->packages.package_select), v2, text_result);
198
199 return cmp_result;
200 }
201
PackageVersionComparatorToString(const PackageVersionComparator pvc)202 const char* PackageVersionComparatorToString(const PackageVersionComparator pvc)
203 {
204 switch (pvc)
205 {
206 case PACKAGE_VERSION_COMPARATOR_EQ: return "==";
207 case PACKAGE_VERSION_COMPARATOR_NONE: return "==";
208 case PACKAGE_VERSION_COMPARATOR_NEQ: return "!=";
209 case PACKAGE_VERSION_COMPARATOR_LT: return "<";
210 case PACKAGE_VERSION_COMPARATOR_GT: return ">";
211 case PACKAGE_VERSION_COMPARATOR_GE: return ">=";
212 case PACKAGE_VERSION_COMPARATOR_LE: return "<=";
213
214 default:
215 ProgrammingError("Unexpected PackageVersionComparator value: %d", pvc);
216 }
217
218 return NULL;
219 }
220