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 <files_edit.h>
26
27 #include <actuator.h>
28 #include <eval_context.h>
29 #include <files_names.h>
30 #include <files_interfaces.h>
31 #include <files_operators.h>
32 #include <files_lib.h>
33 #include <files_editxml.h>
34 #include <item_lib.h>
35 #include <policy.h>
36
37 /*****************************************************************************/
38
NewEditContext(char * filename,const Attributes * a)39 EditContext *NewEditContext(char *filename, const Attributes *a)
40 {
41 EditContext *ec;
42
43 if (!IsAbsoluteFileName(filename))
44 {
45 Log(LOG_LEVEL_ERR, "Relative file name '%s' was marked for editing but has no invariant meaning", filename);
46 return NULL;
47 }
48
49 ec = xcalloc(1, sizeof(EditContext));
50
51 ec->filename = filename;
52
53 /* If making changes in chroot, we need to load the file from the chroot
54 * instead. */
55 if (ChrootChanges())
56 {
57 ec->changes_filename = xstrdup(ToChangesChroot(filename));
58 }
59 else
60 {
61 ec->changes_filename = xstrdup(filename);
62 }
63
64 ec->new_line_mode = FileNewLineMode(ec->changes_filename);
65
66 if (a->haveeditline)
67 {
68 if (!LoadFileAsItemList(&(ec->file_start), ec->changes_filename, a->edits))
69 {
70 free(ec);
71 return NULL;
72 }
73 }
74
75 if (a->haveeditxml)
76 {
77 #ifdef HAVE_LIBXML2
78 if (!LoadFileAsXmlDoc(&(ec->xmldoc), ec->changes_filename, a->edits))
79 {
80 free(ec);
81 return NULL;
82 }
83 #else
84 Log(LOG_LEVEL_ERR, "Cannot edit XML files without LIBXML2");
85 free(ec);
86 return NULL;
87 #endif
88 }
89
90 if (a->edits.empty_before_use)
91 {
92 Log(LOG_LEVEL_VERBOSE, "Build file model from a blank slate (emptying)");
93 DeleteItemList(ec->file_start);
94 ec->file_start = NULL;
95 }
96
97 return ec;
98 }
99
100 /*****************************************************************************/
101
FinishEditContext(EvalContext * ctx,EditContext * ec,const Attributes * a,const Promise * pp,PromiseResult * result)102 void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, const Promise *pp,
103 PromiseResult *result)
104 {
105 if ((*result != PROMISE_RESULT_NOOP) && (*result != PROMISE_RESULT_CHANGE))
106 {
107 // Failure or skipped. Don't update the file.
108 goto end;
109 }
110
111 /* If some edits are to be saved, but we are not making changes to
112 * files (dry-run), just log the fact (MakingChanges() does that). */
113 if ((ec != NULL) && (ec->num_edits > 0) &&
114 !CompareToFile(ctx, ec->file_start, ec->changes_filename, a, pp, result) &&
115 !MakingChanges(ctx, pp, a, result, "edit file '%s'", ec->filename))
116 {
117 goto end;
118 }
119 else if (ec && (ec->num_edits > 0))
120 {
121 if (a->haveeditline || a->edit_template || a->edit_template_string)
122 {
123 if (CompareToFile(ctx, ec->file_start, ec->changes_filename, a, pp, result))
124 {
125 RecordNoChange(ctx, pp, a, "No edit changes to file '%s' need saving",
126 ec->filename);
127 }
128 else if (SaveItemListAsFile(ec->file_start, ec->changes_filename, a, ec->new_line_mode))
129 {
130 RecordChange(ctx, pp, a, "Edited file '%s'", ec->filename);
131 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
132 }
133 else
134 {
135 RecordFailure(ctx, pp, a, "Unable to save file '%s' after editing", ec->filename);
136 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
137 }
138 }
139
140 if (a->haveeditxml)
141 {
142 #ifdef HAVE_LIBXML2
143 if (XmlCompareToFile(ec->xmldoc, ec->changes_filename, a->edits))
144 {
145 if (ec)
146 {
147 RecordNoChange(ctx, pp, a, "No edit changes to xml file '%s' need saving",
148 ec->filename);
149 }
150 }
151 else if (SaveXmlDocAsFile(ec->xmldoc, ec->changes_filename, a, ec->new_line_mode))
152 {
153 RecordChange(ctx, pp, a, "Edited xml file '%s'", ec->filename);
154 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
155 }
156 else
157 {
158 RecordFailure(ctx, pp, a, "Failed to edit XML file '%s'", ec->filename);
159 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
160 }
161 xmlFreeDoc(ec->xmldoc);
162 #else
163 RecordFailure(ctx, pp, a, "Cannot edit XML files without LIBXML2");
164 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
165 #endif
166 }
167 }
168 else if (ec)
169 {
170 RecordNoChange(ctx, pp, a, "No edit changes to file '%s' need saving", ec->filename);
171 }
172
173 end:
174 if (ec != NULL)
175 {
176 DeleteItemList(ec->file_start);
177 free(ec->changes_filename);
178 free(ec);
179 }
180 }
181
182 /*********************************************************************/
183 /* Level */
184 /*********************************************************************/
185
186 #ifdef HAVE_LIBXML2
187 /***************************************************************************/
188
LoadFileAsXmlDoc(xmlDocPtr * doc,const char * file,EditDefaults edits)189 bool LoadFileAsXmlDoc(xmlDocPtr *doc, const char *file, EditDefaults edits)
190 {
191 struct stat statbuf;
192
193 if (stat(file, &statbuf) == -1)
194 {
195 Log(LOG_LEVEL_ERR, "The proposed file '%s' could not be loaded. (stat: %s)", file, GetErrorStr());
196 return false;
197 }
198
199 if (edits.maxfilesize != 0 && statbuf.st_size > edits.maxfilesize)
200 {
201 Log(LOG_LEVEL_INFO, "File '%s' is bigger than the edit limit. max_file_size = '%jd' > '%d' bytes", file,
202 (intmax_t) statbuf.st_size, edits.maxfilesize);
203 return false;
204 }
205
206 if (!S_ISREG(statbuf.st_mode))
207 {
208 Log(LOG_LEVEL_INFO, "'%s' is not a plain file", file);
209 return false;
210 }
211
212 if (statbuf.st_size == 0)
213 {
214 if ((*doc = xmlNewDoc(BAD_CAST "1.0")) == NULL)
215 {
216 Log(LOG_LEVEL_INFO, "Document '%s' not parsed successfully. (xmlNewDoc: %s)", file, GetErrorStr());
217 return false;
218 }
219 }
220 else if ((*doc = xmlParseFile(file)) == NULL)
221 {
222 Log(LOG_LEVEL_INFO, "Document '%s' not parsed successfully. (xmlParseFile: %s)", file, GetErrorStr());
223 return false;
224 }
225
226 return true;
227 }
228
229 /*********************************************************************/
230
SaveXmlCallback(const char * dest_filename,void * param,ARG_UNUSED NewLineMode new_line_mode)231 static bool SaveXmlCallback(const char *dest_filename, void *param,
232 ARG_UNUSED NewLineMode new_line_mode)
233 {
234 xmlDocPtr doc = param;
235
236 //saving xml to file
237 if (xmlSaveFile(dest_filename, doc) == -1)
238 {
239 Log(LOG_LEVEL_ERR, "Failed to write xml document to file '%s' after editing. (xmlSaveFile: %s)", dest_filename, GetErrorStr());
240 return false;
241 }
242
243 return true;
244 }
245
246 /*********************************************************************/
247
SaveXmlDocAsFile(xmlDocPtr doc,const char * file,const Attributes * a,NewLineMode new_line_mode)248 bool SaveXmlDocAsFile(xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode)
249 {
250 return SaveAsFile(&SaveXmlCallback, doc, file, a, new_line_mode);
251 }
252 #endif /* HAVE_LIBXML2 */
253