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 <platform.h>
26 #include <alloc.h> /* xstrdup() */
27 #include <dir.h> /* DirOpen(), DirRead(), DirClose() */
28 #include <eval_context.h> /* ToChangesChroot() */
29 #include <file_lib.h> /* FILE_SEPARATOR */
30 #include <files_lib.h> /* MakeParentDirectory() */
31 #include <files_copy.h> /* CopyRegularFileDisk(), CopyFilePermissionsDisk() */
32 #include <files_names.h> /* IsAbsPath(), JoinPaths() */
33 #include <files_links.h> /* ExpandLinks() */
34 #include <string_lib.h> /* StringEqual() */
35 #include <string_sequence.h> /* WriteLenPrefixedString() */
36 #include <writer.h> /* FileWriter(), Writer */
37 #include <csv_writer.h> /* CsvWriter */
38
39 #include <changes_chroot.h>
40
41
GetLastFileSeparator(const char * path,const char * end)42 static inline const char *GetLastFileSeparator(const char *path, const char *end)
43 {
44 const char *cp = end;
45 for (; (cp > path) && (*cp != FILE_SEPARATOR); cp--);
46 return cp;
47 }
48
GetFirstNonExistingParentDir(const char * path,char * buf)49 static char *GetFirstNonExistingParentDir(const char *path, char *buf)
50 {
51 /* Get rid of the trailing file separator (if any). */
52 size_t path_len = strlen(path);
53 if (path[path_len - 1] == FILE_SEPARATOR)
54 {
55 strncpy(buf, path, path_len);
56 buf[path_len] = '\0';
57 }
58 else
59 {
60 strncpy(buf, path, path_len + 1);
61 }
62
63 char *last_sep = (char *) GetLastFileSeparator(buf, buf + path_len);
64 while (last_sep != buf)
65 {
66 *last_sep = '\0';
67 if (access(buf, F_OK) == 0)
68 {
69 *last_sep = FILE_SEPARATOR;
70 *(last_sep + 1) = '\0';
71 return buf;
72 }
73 last_sep = (char *) GetLastFileSeparator(buf, last_sep - 1);
74 }
75 *last_sep = '\0';
76 return buf;
77 }
78
MirrorDirTreePermsToChroot(const char * path)79 static bool MirrorDirTreePermsToChroot(const char *path)
80 {
81 const char *chrooted = ToChangesChroot(path);
82
83 if (!CopyFilePermissionsDisk(path, chrooted))
84 {
85 return false;
86 }
87
88 size_t path_len = strlen(path);
89 char path_copy[path_len + 1];
90 strcpy(path_copy, path);
91
92 const char *const path_copy_end = path_copy + (path_len - 1);
93 const char *const chrooted_end = chrooted + strlen(chrooted) - 1;
94
95 char *last_sep = (char *) GetLastFileSeparator(path_copy, path_copy_end);
96 while (last_sep != path_copy)
97 {
98 char *last_sep_chrooted = (char *) chrooted_end - (path_copy_end - last_sep);
99 *last_sep = '\0';
100 *last_sep_chrooted = '\0';
101 if (!CopyFilePermissionsDisk(path_copy, chrooted))
102 {
103 return false;
104 }
105 *last_sep = FILE_SEPARATOR;
106 *last_sep_chrooted = FILE_SEPARATOR;
107 last_sep = (char *) GetLastFileSeparator(path_copy, last_sep - 1);
108 }
109 return true;
110 }
111
112 #ifndef __MINGW32__
113 /**
114 * Mirror the symlink #path to #chrooted_path together with its target, then
115 * mirror the target if it is a symlink too,..., recursively.
116 */
ChrootSymlinkDeep(const char * path,const char * chrooted_path,struct stat * sb)117 static void ChrootSymlinkDeep(const char *path, const char *chrooted_path, struct stat *sb)
118 {
119 assert(sb != NULL);
120
121 size_t target_size = (sb->st_size != 0 ? sb->st_size + 1 : PATH_MAX);
122 char target[target_size];
123 ssize_t ret = readlink(path, target, target_size);
124 if (ret == -1)
125 {
126 /* Should never happen, but nothing to do here if it does. */
127 return;
128 }
129 target[ret] = '\0';
130
131 if (IsAbsPath(target))
132 {
133 const char *chrooted_target = ToChangesChroot(target);
134 if (symlink(chrooted_target, chrooted_path) != 0)
135 {
136 /* Should never happen, but nothing to do here if it does. */
137 return;
138 }
139 else
140 {
141 PrepareChangesChroot(target);
142 }
143 }
144 else
145 {
146 if (symlink(target, chrooted_path) != 0)
147 {
148 /* Should never happen, but nothing to do here if it does. */
149 return;
150 }
151 else
152 {
153 char expanded_target[PATH_MAX];
154 if (!ExpandLinks(expanded_target, path, 0, 1))
155 {
156 /* Should never happen, but nothing to do here if it does. */
157 return;
158 }
159 PrepareChangesChroot(expanded_target);
160 }
161 }
162 }
163 #endif /* __MINGW32__ */
164
PrepareChangesChroot(const char * path)165 void PrepareChangesChroot(const char *path)
166 {
167 struct stat sb;
168
169 /* We need to create a copy because ToChangesChroot() returns a pointer to
170 * its internal buffer which gets overwritten by later calls of the
171 * function. */
172 char *chrooted = xstrdup(ToChangesChroot(path));
173 if (lstat(chrooted, &sb) != -1)
174 {
175 /* chrooted 'path' already exists, we are done */
176 free(chrooted);
177 return;
178 }
179
180 {
181 char first_nonexisting_parent[PATH_MAX];
182 GetFirstNonExistingParentDir(path, first_nonexisting_parent);
183 MakeParentDirectory(first_nonexisting_parent, true, NULL);
184 MirrorDirTreePermsToChroot(first_nonexisting_parent);
185 }
186
187 if (lstat(path, &sb) == -1)
188 {
189 /* 'path' doesn't exist, nothing to do here */
190 return;
191 }
192
193 #ifndef __MINGW32__
194 if (S_ISLNK(sb.st_mode))
195 {
196 ChrootSymlinkDeep(path, chrooted, &sb);
197 }
198 else
199 #endif /* __MINGW32__ */
200 if (S_ISDIR(sb.st_mode))
201 {
202 mkdir(chrooted, sb.st_mode);
203 Dir *dir = DirOpen(path);
204 if (dir == NULL)
205 {
206 /* Should never happen, but nothing to do here if it does. */
207 free(chrooted);
208 return;
209 }
210
211 for (const struct dirent *entry = DirRead(dir); entry != NULL; entry = DirRead(dir))
212 {
213 if (StringEqual(entry->d_name, ".") || StringEqual(entry->d_name, ".."))
214 {
215 continue;
216 }
217 char entry_path[PATH_MAX];
218 strcpy(entry_path, path);
219 JoinPaths(entry_path, PATH_MAX, entry->d_name);
220 PrepareChangesChroot(entry_path);
221 }
222 DirClose(dir);
223 }
224 else
225 {
226 /* TODO: sockets, pipes, devices,... ? */
227 CopyRegularFileDisk(path, chrooted);
228 }
229 CopyFilePermissionsDisk(path, chrooted);
230
231 free(chrooted);
232 }
233
RecordFileChangedInChroot(const char * path)234 bool RecordFileChangedInChroot(const char *path)
235 {
236 FILE *chroot_changes = safe_fopen(ToChangesChroot(CHROOT_CHANGES_LIST_FILE), "a");
237 Writer *writer = FileWriter(chroot_changes);
238
239 bool ret = WriteLenPrefixedString(writer, path);
240
241 WriterClose(writer);
242 return ret;
243 }
244
RecordFileRenamedInChroot(const char * old_name,const char * new_name)245 bool RecordFileRenamedInChroot(const char *old_name, const char *new_name)
246 {
247 FILE *chroot_renames = safe_fopen(ToChangesChroot(CHROOT_RENAMES_LIST_FILE), "a");
248 Writer *writer = FileWriter(chroot_renames);
249
250 bool ret = WriteLenPrefixedString(writer, old_name);
251 ret = (ret && WriteLenPrefixedString(writer, new_name));
252
253 WriterClose(writer);
254 return ret;
255 }
256
RecordFileEvaluatedInChroot(const char * path)257 bool RecordFileEvaluatedInChroot(const char *path)
258 {
259 FILE *chroot_changes = safe_fopen(ToChangesChroot(CHROOT_KEPT_LIST_FILE), "a");
260 Writer *writer = FileWriter(chroot_changes);
261
262 bool ret = WriteLenPrefixedString(writer, path);
263
264 WriterClose(writer);
265 return ret;
266 }
267
RecordPkgOperationInChroot(const char * op,const char * name,const char * arch,const char * version)268 bool RecordPkgOperationInChroot(const char *op, const char *name, const char *arch, const char *version)
269 {
270 assert(op != NULL);
271 assert(name != NULL);
272 /* the rest is optional */
273
274 FILE *chroot_pkgs_ops = safe_fopen(ToChangesChroot(CHROOT_PKGS_OPS_FILE), "a");
275 if (chroot_pkgs_ops == NULL)
276 {
277 Log(LOG_LEVEL_ERR, "Failed to open package operations record file '%s'",
278 CHROOT_PKGS_OPS_FILE);
279 return false;
280 }
281
282 Writer *writer = FileWriter(chroot_pkgs_ops);
283 if (writer == NULL)
284 {
285 Log(LOG_LEVEL_ERR, "Failed to create a writer for package operations record file '%s'",
286 CHROOT_PKGS_OPS_FILE);
287 fclose(chroot_pkgs_ops);
288 return false;
289 }
290
291 CsvWriter *csv_writer = CsvWriterOpen(writer);
292 if (csv_writer == NULL)
293 {
294 Log(LOG_LEVEL_ERR, "Failed to create a CSV writer for package operations record file '%s'",
295 CHROOT_PKGS_OPS_FILE);
296 WriterClose(writer);
297 return false;
298 }
299
300 CsvWriterField(csv_writer, op);
301 CsvWriterField(csv_writer, name);
302 CsvWriterField(csv_writer, NULL_TO_EMPTY_STRING(arch));
303 CsvWriterField(csv_writer, NULL_TO_EMPTY_STRING(version));
304
305 CsvWriterNewRecord(csv_writer);
306 CsvWriterClose(csv_writer);
307 WriterClose(writer);
308
309 return true;
310 }
311