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