1 /*
2   Copyright 2020 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 <verify_new_packages.h>
26 #include <package_module.h>
27 #include <logging.h>
28 #include <string_lib.h>
29 #include <locks.h>
30 #include <ornaments.h>
31 
NewPackagePromiseSanityCheck(const Attributes * a)32 static bool NewPackagePromiseSanityCheck(const Attributes *a)
33 {
34     assert(a != NULL);
35     if (!a->new_packages.module_body || !a->new_packages.module_body->name)
36     {
37         Log(LOG_LEVEL_ERR, "Can not find package module body in policy.");
38         return false;
39     }
40 
41     if (a->new_packages.module_body->updates_ifelapsed == CF_NOINT ||
42         a->new_packages.module_body->installed_ifelapsed == CF_NOINT)
43     {
44         Log(LOG_LEVEL_ERR,
45                 "Invalid or missing arguments in package_module body '%s':  "
46                 "query_installed_ifelapsed = %d query_updates_ifelapsed = %d",
47                 a->new_packages.module_body->name,
48                 a->new_packages.module_body->installed_ifelapsed,
49                 a->new_packages.module_body->updates_ifelapsed);
50             return false;
51         return false;
52     }
53 
54     if (a->new_packages.package_policy == NEW_PACKAGE_ACTION_NONE)
55     {
56         Log(LOG_LEVEL_ERR, "Unsupported package policy in package promise.");
57         return false;
58     }
59     return true;
60 }
61 
HandleNewPackagePromiseType(EvalContext * ctx,const Promise * pp,const Attributes * a,char ** promise_log_msg,LogLevel * log_lvl)62 PromiseResult HandleNewPackagePromiseType(EvalContext *ctx, const Promise *pp,
63                                           const Attributes *a, char **promise_log_msg,
64                                           LogLevel *log_lvl)
65 {
66     assert(a != NULL);
67     Log(LOG_LEVEL_DEBUG, "New package promise handler");
68 
69 
70     if (!NewPackagePromiseSanityCheck(a))
71     {
72         *promise_log_msg =
73                 SafeStringDuplicate("New package promise failed sanity check.");
74         *log_lvl = LOG_LEVEL_ERR;
75         return PROMISE_RESULT_FAIL;
76     }
77 
78     PromiseBanner(ctx, pp);
79 
80     PackagePromiseGlobalLock global_lock = AcquireGlobalPackagePromiseLock(ctx);
81 
82     CfLock package_promise_lock;
83     char promise_lock[CF_BUFSIZE];
84     snprintf(promise_lock, sizeof(promise_lock), "new-package-%s-%s",
85              pp->promiser, a->new_packages.module_body->name);
86 
87     if (global_lock.g_lock.lock == NULL)
88     {
89         *promise_log_msg =
90                 SafeStringDuplicate("Can not acquire global lock for package "
91                                     "promise. Skipping promise evaluation");
92         *log_lvl = LOG_LEVEL_INFO;
93 
94         return PROMISE_RESULT_SKIPPED;
95     }
96 
97     package_promise_lock =
98             AcquireLock(ctx, promise_lock, VUQNAME, CFSTARTTIME,
99             a->transaction.ifelapsed, a->transaction.expireafter, pp, false);
100     if (package_promise_lock.lock == NULL)
101     {
102         Log(LOG_LEVEL_DEBUG, "Skipping promise execution due to locking.");
103         YieldGlobalPackagePromiseLock(global_lock);
104 
105         *promise_log_msg =
106                 StringFormat("Can not acquire lock for '%s' package promise. "
107                              "Skipping promise evaluation",  pp->promiser);
108         *log_lvl = LOG_LEVEL_VERBOSE;
109 
110         return PROMISE_RESULT_SKIPPED;
111     }
112 
113     PackageModuleWrapper *package_module =
114             NewPackageModuleWrapper(a->new_packages.module_body);
115 
116     if (!package_module)
117     {
118         *promise_log_msg =
119                 StringFormat("Some error occurred while contacting package "
120                              "module - promise: %s", pp->promiser);
121         *log_lvl = LOG_LEVEL_ERR;
122 
123         YieldCurrentLock(package_promise_lock);
124         YieldGlobalPackagePromiseLock(global_lock);
125 
126         return PROMISE_RESULT_FAIL;
127     }
128 
129     PromiseResult result = PROMISE_RESULT_FAIL;
130 
131     switch (a->new_packages.package_policy)
132     {
133         case NEW_PACKAGE_ACTION_ABSENT:
134             result = HandleAbsentPromiseAction(ctx, pp->promiser,
135                                                &a->new_packages,
136                                                package_module,
137                                                a->transaction.action);
138 
139             switch (result)
140             {
141                 case PROMISE_RESULT_FAIL:
142                     *log_lvl = LOG_LEVEL_ERR;
143                     *promise_log_msg =
144                         StringFormat("Error removing package '%s'",
145                                      pp->promiser);
146                     break;
147                 case PROMISE_RESULT_CHANGE:
148                     *log_lvl = LOG_LEVEL_INFO;
149                     *promise_log_msg =
150                         StringFormat("Successfully removed package '%s'",
151                                      pp->promiser);
152                     break;
153                 case PROMISE_RESULT_NOOP:
154                     *log_lvl = LOG_LEVEL_VERBOSE;
155                     *promise_log_msg =
156                         StringFormat("Package '%s' was not installed",
157                                      pp->promiser);
158                     break;
159                 case PROMISE_RESULT_WARN:
160                     *log_lvl = LOG_LEVEL_WARNING;
161                     *promise_log_msg =
162                         StringFormat("Package '%s' needs to be removed,"
163                                      "but only warning was promised",
164                                      pp->promiser);
165                     break;
166                 default:
167                     ProgrammingError(
168                             "Absent promise action evaluation returned "
169                              "unsupported result: %d",
170                             result);
171                     break;
172             }
173             break;
174         case NEW_PACKAGE_ACTION_PRESENT:
175             result = HandlePresentPromiseAction(ctx, pp->promiser,
176                                                 &a->new_packages,
177                                                 package_module,
178                                                 a->transaction.action);
179 
180             switch (result)
181             {
182                 case PROMISE_RESULT_FAIL:
183                     *log_lvl = LOG_LEVEL_ERR;
184                     *promise_log_msg =
185                         StringFormat("Error installing package '%s'",
186                                      pp->promiser);
187                     break;
188                 case PROMISE_RESULT_CHANGE:
189                     *log_lvl = LOG_LEVEL_INFO;
190                     *promise_log_msg =
191                         StringFormat("Successfully installed package '%s'",
192                                      pp->promiser);
193                     break;
194                 case PROMISE_RESULT_NOOP:
195                     *log_lvl = LOG_LEVEL_VERBOSE;
196                     *promise_log_msg =
197                         StringFormat("Package '%s' already installed",
198                                      pp->promiser);
199                     break;
200                 case PROMISE_RESULT_WARN:
201                     *log_lvl = LOG_LEVEL_WARNING;
202                     *promise_log_msg =
203                         StringFormat("Package '%s' needs to be installed,"
204                                      "but only warning was promised",
205                                      pp->promiser);
206                     break;
207                 default:
208                     ProgrammingError(
209                             "Present promise action evaluation returned "
210                              "unsupported result: %d",
211                             result);
212                     break;
213             }
214 
215             break;
216         case NEW_PACKAGE_ACTION_NONE:
217         default:
218             ProgrammingError("Unsupported package action: %d",
219                              a->new_packages.package_policy);
220             break;
221     }
222 
223     DeletePackageModuleWrapper(package_module);
224 
225     YieldCurrentLock(package_promise_lock);
226     YieldGlobalPackagePromiseLock(global_lock);
227 
228     return result;
229 }
230