/src/ostree/src/libostree/ostree-repo.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2011 Colin Walters <walters@verbum.org> |
3 | | * Copyright (C) 2015 Red Hat, Inc. |
4 | | * Copyright (C) 2022 Igalia S.L. |
5 | | * |
6 | | * SPDX-License-Identifier: LGPL-2.0+ |
7 | | * |
8 | | * This library is free software; you can redistribute it and/or |
9 | | * modify it under the terms of the GNU Lesser General Public |
10 | | * License as published by the Free Software Foundation; either |
11 | | * version 2 of the License, or (at your option) any later version. |
12 | | * |
13 | | * This library is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | | * Lesser General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU Lesser General Public |
19 | | * License along with this library. If not, see <https://www.gnu.org/licenses/>. |
20 | | * |
21 | | * Author: Colin Walters <walters@verbum.org> |
22 | | */ |
23 | | |
24 | | #include "config.h" |
25 | | |
26 | | #include "libglnx.h" |
27 | | #include "ostree-core.h" |
28 | | #include "otutil.h" |
29 | | #include <gio/gfiledescriptorbased.h> |
30 | | #include <gio/gunixinputstream.h> |
31 | | #include <gio/gunixoutputstream.h> |
32 | | #include <glib-unix.h> |
33 | | #include <glnx-console.h> |
34 | | #include <linux/magic.h> |
35 | | |
36 | | #include "ostree-autocleanups.h" |
37 | | #include "ostree-core-private.h" |
38 | | #include "ostree-gpg-verifier.h" |
39 | | #include "ostree-remote-private.h" |
40 | | #include "ostree-repo-file-enumerator.h" |
41 | | #include "ostree-repo-file.h" |
42 | | #include "ostree-repo-private.h" |
43 | | #include "ostree-repo-static-delta-private.h" |
44 | | #include "ostree-sign-private.h" |
45 | | #include "ostree-sysroot-private.h" |
46 | | #include "ot-fs-utils.h" |
47 | | |
48 | | #include <glib/gstdio.h> |
49 | | #include <locale.h> |
50 | | #include <sys/file.h> |
51 | | #include <sys/statfs.h> |
52 | | #include <sys/statvfs.h> |
53 | | |
54 | 0 | #define REPO_LOCK_DISABLED (-2) |
55 | 0 | #define REPO_LOCK_BLOCKING (-1) |
56 | | |
57 | | /* ABI Size checks for ostree-repo.h, only for LP64 systems; |
58 | | * https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models |
59 | | * |
60 | | * To generate this data, I used `pahole` from gdb. More concretely, `gdb --args |
61 | | * /usr/bin/ostree`, then `start`, (to ensure debuginfo was loaded), then e.g. |
62 | | * `$ pahole OstreeRepoTransactionStats`. |
63 | | */ |
64 | | #if __SIZEOF_POINTER__ == 8 && __SIZEOF_LONG__ == 8 && __SIZEOF_INT__ == 4 |
65 | | G_STATIC_ASSERT (sizeof (OstreeRepoTransactionStats) == sizeof (int) * 4 + 8 * 5); |
66 | | G_STATIC_ASSERT (sizeof (OstreeRepoImportArchiveOptions) |
67 | | == sizeof (int) * 9 + 4 + sizeof (void *) * 8); |
68 | | G_STATIC_ASSERT (sizeof (OstreeRepoExportArchiveOptions) |
69 | | == sizeof (int) * 9 + 4 + 8 + sizeof (void *) * 8); |
70 | | G_STATIC_ASSERT (sizeof (OstreeRepoCheckoutAtOptions) |
71 | | == sizeof (OstreeRepoCheckoutMode) + sizeof (OstreeRepoCheckoutOverwriteMode) |
72 | | + sizeof (int) * 6 + sizeof (int) * 5 + sizeof (int) + sizeof (void *) * 2 |
73 | | + sizeof (int) * 6 + sizeof (void *) * 7); |
74 | | G_STATIC_ASSERT (sizeof (OstreeRepoCommitTraverseIter) |
75 | | == sizeof (int) + sizeof (int) + sizeof (void *) * 10 + 130 + 6); /* 6 byte hole */ |
76 | | G_STATIC_ASSERT (sizeof (OstreeRepoPruneOptions) |
77 | | == sizeof (OstreeRepoPruneFlags) + 4 + sizeof (void *) + sizeof (int) * 12 |
78 | | + sizeof (void *) * 7); |
79 | | #endif |
80 | | |
81 | | /** |
82 | | * SECTION:ostree-repo |
83 | | * @title: OstreeRepo: Content-addressed object store |
84 | | * @short_description: A git-like storage system for operating system binaries |
85 | | * |
86 | | * The #OstreeRepo is like git, a content-addressed object store. |
87 | | * Unlike git, it records uid, gid, and extended attributes. |
88 | | * |
89 | | * There are four possible "modes" for an #OstreeRepo; %OSTREE_REPO_MODE_BARE |
90 | | * is very simple - content files are represented exactly as they are, and |
91 | | * checkouts are just hardlinks. %OSTREE_REPO_MODE_BARE_USER is similar, except |
92 | | * the uid/gids are not set on the files, and checkouts as hardlinks work only |
93 | | * for user checkouts. %OSTREE_REPO_MODE_BARE_USER_ONLY is the same as |
94 | | * BARE_USER, but all metadata is not stored, so it can only be used for user |
95 | | * checkouts. This mode does not require xattrs. A %OSTREE_REPO_MODE_ARCHIVE |
96 | | * (also known as %OSTREE_REPO_MODE_ARCHIVE_Z2) repository in contrast stores |
97 | | * content files zlib-compressed. It is suitable for non-root-owned |
98 | | * repositories that can be served via a static HTTP server. |
99 | | * |
100 | | * Creating an #OstreeRepo does not invoke any file I/O, and thus needs |
101 | | * to be initialized, either from existing contents or as a new |
102 | | * repository. If you have an existing repo, use ostree_repo_open() |
103 | | * to load it from disk and check its validity. To initialize a new |
104 | | * repository in the given filepath, use ostree_repo_create() instead. |
105 | | * |
106 | | * To store content in the repo, first start a transaction with |
107 | | * ostree_repo_prepare_transaction(). Then create a |
108 | | * #OstreeMutableTree, and apply functions such as |
109 | | * ostree_repo_write_directory_to_mtree() to traverse a physical |
110 | | * filesystem and write content, possibly multiple times. |
111 | | * |
112 | | * Once the #OstreeMutableTree is complete, write all of its metadata |
113 | | * with ostree_repo_write_mtree(), and finally create a commit with |
114 | | * ostree_repo_write_commit(). |
115 | | * |
116 | | * ## Collection IDs |
117 | | * |
118 | | * A collection ID is a globally unique identifier which, if set, is used to |
119 | | * identify refs from a repository which are mirrored elsewhere, such as in |
120 | | * mirror repositories or peer to peer networks. |
121 | | * |
122 | | * This is separate from the `collection-id` configuration key for a remote, which |
123 | | * is used to store the collection ID of the repository that remote points to. |
124 | | * |
125 | | * The collection ID should only be set on an #OstreeRepo if it is the canonical |
126 | | * collection for some refs. |
127 | | * |
128 | | * A collection ID must be a reverse DNS name, where the domain name is under the |
129 | | * control of the curator of the collection, so they can demonstrate ownership |
130 | | * of the collection. The later elements in the reverse DNS name can be used to |
131 | | * disambiguate between multiple collections from the same curator. For example, |
132 | | * `org.exampleos.Main` and `org.exampleos.Apps`. For the complete format of |
133 | | * collection IDs, see ostree_validate_collection_id(). |
134 | | */ |
135 | | typedef struct |
136 | | { |
137 | | GObjectClass parent_class; |
138 | | |
139 | | #ifndef OSTREE_DISABLE_GPGME |
140 | | void (*gpg_verify_result) (OstreeRepo *self, const char *checksum, OstreeGpgVerifyResult *result); |
141 | | #endif |
142 | | } OstreeRepoClass; |
143 | | |
144 | | enum |
145 | | { |
146 | | PROP_0, |
147 | | |
148 | | PROP_PATH, |
149 | | PROP_REMOTES_CONFIG_DIR, |
150 | | PROP_SYSROOT_PATH |
151 | | }; |
152 | | |
153 | | enum |
154 | | { |
155 | | GPG_VERIFY_RESULT, |
156 | | LAST_SIGNAL |
157 | | }; |
158 | | |
159 | | #ifndef OSTREE_DISABLE_GPGME |
160 | | static guint signals[LAST_SIGNAL] = { 0 }; |
161 | | #endif |
162 | | |
163 | 1.52k | G_DEFINE_TYPE (OstreeRepo, ostree_repo, G_TYPE_OBJECT) |
164 | 1.52k | |
165 | 1.52k | #define SYSCONF_REMOTES SHORTENED_SYSCONFDIR "/ostree/remotes.d" |
166 | | |
167 | | /* Repository locking |
168 | | * |
169 | | * To guard against objects being deleted (e.g., prune) while they're in |
170 | | * use by another operation that is accessing them (e.g., commit), the |
171 | | * repository must be locked by concurrent writers. |
172 | | * |
173 | | * The repository locking has several important features: |
174 | | * |
175 | | * * There are 2 states - shared and exclusive. Multiple users can hold |
176 | | * a shared lock concurrently while only one user can hold an |
177 | | * exclusive lock. |
178 | | * |
179 | | * * The lock can be taken recursively so long as each acquisition is paired |
180 | | * with a matching release. The recursion is also latched to the strongest |
181 | | * state. Once an exclusive lock has been taken, it will remain exclusive |
182 | | * until all exclusive locks have been released. |
183 | | * |
184 | | * * It is both multiprocess- and multithread-safe. Threads that share |
185 | | * an OstreeRepo use the lock cooperatively while processes and |
186 | | * threads using separate OstreeRepo structures will block when |
187 | | * acquiring incompatible lock states. |
188 | | * |
189 | | * The actual locking is implemented using either open file descriptor |
190 | | * locks or flock locks. This allows the locking to work with concurrent |
191 | | * processes or concurrent threads using a separate OstreeRepo. The lock |
192 | | * file is held on the ".lock" file within the repository. |
193 | | * |
194 | | * The intended usage is to take a shared lock when writing objects or |
195 | | * reading objects in critical sections. Exclusive locks are taken when |
196 | | * deleting objects. |
197 | | * |
198 | | * To allow fine grained locking, the lock state is maintained in shared and |
199 | | * exclusive counters. Callers then push or pop lock types to increment or |
200 | | * decrement the counters. When pushing or popping a lock type identical to |
201 | | * the existing or next state, the lock state is simply updated. Only when |
202 | | * upgrading or downgrading the lock (changing to/from unlocked, pushing |
203 | | * exclusive on shared or popping exclusive to shared) are actual locking |
204 | | * operations performed. |
205 | | */ |
206 | | |
207 | | typedef struct |
208 | | { |
209 | | guint len; |
210 | | int state; |
211 | | const char *name; |
212 | | } OstreeRepoLockInfo; |
213 | | |
214 | | static const char * |
215 | | lock_state_name (int state) |
216 | 0 | { |
217 | 0 | switch (state) |
218 | 0 | { |
219 | 0 | case LOCK_EX: |
220 | 0 | return "exclusive"; |
221 | 0 | case LOCK_SH: |
222 | 0 | return "shared"; |
223 | 0 | case LOCK_UN: |
224 | 0 | return "unlocked"; |
225 | 0 | default: |
226 | 0 | g_assert_not_reached (); |
227 | 0 | } |
228 | 0 | } |
229 | | |
230 | | static void |
231 | | repo_lock_info (OstreeRepo *self, GMutexLocker *locker, OstreeRepoLockInfo *out_info) |
232 | 0 | { |
233 | 0 | g_assert (self != NULL); |
234 | 0 | g_assert (locker != NULL); |
235 | 0 | g_assert (out_info != NULL); |
236 | | |
237 | 0 | OstreeRepoLockInfo info; |
238 | 0 | info.len = self->lock.shared + self->lock.exclusive; |
239 | 0 | if (info.len == 0) |
240 | 0 | info.state = LOCK_UN; |
241 | 0 | else if (self->lock.exclusive > 0) |
242 | 0 | info.state = LOCK_EX; |
243 | 0 | else |
244 | 0 | info.state = LOCK_SH; |
245 | 0 | info.name = lock_state_name (info.state); |
246 | |
|
247 | 0 | *out_info = info; |
248 | 0 | } |
249 | | |
250 | | /* Wrapper to handle flock vs OFD locking based on GLnxLockFile */ |
251 | | static gboolean |
252 | | do_repo_lock (int fd, int flags) |
253 | 0 | { |
254 | 0 | int res; |
255 | |
|
256 | 0 | #ifdef F_OFD_SETLK |
257 | 0 | struct flock fl = { |
258 | 0 | .l_type = (flags & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK, |
259 | 0 | .l_whence = SEEK_SET, |
260 | 0 | .l_start = 0, |
261 | 0 | .l_len = 0, |
262 | 0 | }; |
263 | |
|
264 | 0 | res = TEMP_FAILURE_RETRY (fcntl (fd, (flags & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl)); |
265 | | #else |
266 | | res = -1; |
267 | | errno = EINVAL; |
268 | | #endif |
269 | | |
270 | | /* Fallback to flock when OFD locks not available */ |
271 | 0 | if (res < 0) |
272 | 0 | { |
273 | 0 | if (errno == EINVAL) |
274 | 0 | res = TEMP_FAILURE_RETRY (flock (fd, flags)); |
275 | 0 | if (res < 0) |
276 | 0 | return FALSE; |
277 | 0 | } |
278 | | |
279 | 0 | return TRUE; |
280 | 0 | } |
281 | | |
282 | | /* Wrapper to handle flock vs OFD unlocking based on GLnxLockFile */ |
283 | | static gboolean |
284 | | do_repo_unlock (int fd, int flags) |
285 | 0 | { |
286 | 0 | int res; |
287 | |
|
288 | 0 | #ifdef F_OFD_SETLK |
289 | 0 | struct flock fl = { |
290 | 0 | .l_type = F_UNLCK, |
291 | 0 | .l_whence = SEEK_SET, |
292 | 0 | .l_start = 0, |
293 | 0 | .l_len = 0, |
294 | 0 | }; |
295 | |
|
296 | 0 | res = TEMP_FAILURE_RETRY (fcntl (fd, (flags & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl)); |
297 | | #else |
298 | | res = -1; |
299 | | errno = EINVAL; |
300 | | #endif |
301 | | |
302 | | /* Fallback to flock when OFD locks not available */ |
303 | 0 | if (res < 0) |
304 | 0 | { |
305 | 0 | if (errno == EINVAL) |
306 | 0 | res = TEMP_FAILURE_RETRY (flock (fd, LOCK_UN | flags)); |
307 | 0 | if (res < 0) |
308 | 0 | return FALSE; |
309 | 0 | } |
310 | | |
311 | 0 | return TRUE; |
312 | 0 | } |
313 | | |
314 | | static gboolean |
315 | | push_repo_lock (OstreeRepo *self, OstreeRepoLockType lock_type, gboolean blocking, GError **error) |
316 | 0 | { |
317 | 0 | int flags = (lock_type == OSTREE_REPO_LOCK_EXCLUSIVE) ? LOCK_EX : LOCK_SH; |
318 | 0 | int next_state = flags; |
319 | 0 | if (!blocking) |
320 | 0 | flags |= LOCK_NB; |
321 | |
|
322 | 0 | g_autoptr (GMutexLocker) locker = g_mutex_locker_new (&self->lock.mutex); |
323 | |
|
324 | 0 | if (self->lock.fd == -1) |
325 | 0 | { |
326 | 0 | g_debug ("Opening repo lock file"); |
327 | 0 | self->lock.fd = TEMP_FAILURE_RETRY ( |
328 | 0 | openat (self->repo_dir_fd, ".lock", O_CREAT | O_RDWR | O_CLOEXEC, DEFAULT_REGFILE_MODE)); |
329 | 0 | if (self->lock.fd < 0) |
330 | 0 | return glnx_throw_errno_prefix (error, "Opening lock file %s/.lock failed", |
331 | 0 | gs_file_get_path_cached (self->repodir)); |
332 | 0 | } |
333 | | |
334 | 0 | OstreeRepoLockInfo info; |
335 | 0 | repo_lock_info (self, locker, &info); |
336 | 0 | g_debug ("Push lock: state=%s, depth=%u", info.name, info.len); |
337 | |
|
338 | 0 | guint *counter; |
339 | 0 | if (next_state == LOCK_EX) |
340 | 0 | counter = &(self->lock.exclusive); |
341 | 0 | else |
342 | 0 | counter = &(self->lock.shared); |
343 | | |
344 | | /* Check for overflow */ |
345 | 0 | if (*counter == G_MAXUINT) |
346 | 0 | g_error ("Repo lock %s counter would overflow", lock_state_name (next_state)); |
347 | |
|
348 | 0 | if (info.state == LOCK_EX || info.state == next_state) |
349 | 0 | { |
350 | 0 | g_debug ("Repo already locked %s, maintaining state", info.name); |
351 | 0 | } |
352 | 0 | else |
353 | 0 | { |
354 | | /* We should never upgrade from exclusive to shared */ |
355 | 0 | g_assert (!(info.state == LOCK_EX && next_state == LOCK_SH)); |
356 | | |
357 | 0 | const char *next_state_name = lock_state_name (next_state); |
358 | 0 | g_debug ("Locking repo %s", next_state_name); |
359 | 0 | if (!do_repo_lock (self->lock.fd, flags)) |
360 | 0 | return glnx_throw_errno_prefix (error, "Locking repo %s failed", next_state_name); |
361 | 0 | } |
362 | | |
363 | | /* Update state */ |
364 | 0 | (*counter)++; |
365 | |
|
366 | 0 | return TRUE; |
367 | 0 | } |
368 | | |
369 | | static gboolean |
370 | | pop_repo_lock (OstreeRepo *self, OstreeRepoLockType lock_type, gboolean blocking, GError **error) |
371 | 0 | { |
372 | 0 | int flags = blocking ? 0 : LOCK_NB; |
373 | |
|
374 | 0 | g_autoptr (GMutexLocker) locker = g_mutex_locker_new (&self->lock.mutex); |
375 | 0 | if (self->lock.fd == -1) |
376 | 0 | g_error ("Cannot pop repo never locked repo lock"); |
377 | |
|
378 | 0 | OstreeRepoLockInfo info; |
379 | 0 | repo_lock_info (self, locker, &info); |
380 | 0 | g_debug ("Pop lock: state=%s, depth=%u", info.name, info.len); |
381 | |
|
382 | 0 | if (info.len == 0 || info.state == LOCK_UN) |
383 | 0 | g_error ("Cannot pop already unlocked repo lock"); |
384 | |
|
385 | 0 | int state_to_drop; |
386 | 0 | guint *counter; |
387 | 0 | if (lock_type == OSTREE_REPO_LOCK_EXCLUSIVE) |
388 | 0 | { |
389 | 0 | state_to_drop = LOCK_EX; |
390 | 0 | counter = &(self->lock.exclusive); |
391 | 0 | } |
392 | 0 | else |
393 | 0 | { |
394 | 0 | state_to_drop = LOCK_SH; |
395 | 0 | counter = &(self->lock.shared); |
396 | 0 | } |
397 | | |
398 | | /* Make sure caller specified a valid type to release */ |
399 | 0 | if (*counter == 0) |
400 | 0 | g_error ("Repo %s lock pop requested, but none have been taken", |
401 | 0 | lock_state_name (state_to_drop)); |
402 | |
|
403 | 0 | int next_state; |
404 | 0 | if (info.len == 1) |
405 | 0 | { |
406 | | /* Lock counters will be empty, unlock */ |
407 | 0 | next_state = LOCK_UN; |
408 | 0 | } |
409 | 0 | else if (state_to_drop == LOCK_EX) |
410 | 0 | next_state = (self->lock.exclusive > 1) ? LOCK_EX : LOCK_SH; |
411 | 0 | else |
412 | 0 | next_state = (self->lock.exclusive > 0) ? LOCK_EX : LOCK_SH; |
413 | |
|
414 | 0 | if (next_state == LOCK_UN) |
415 | 0 | { |
416 | 0 | g_debug ("Unlocking repo"); |
417 | 0 | if (!do_repo_unlock (self->lock.fd, flags)) |
418 | 0 | return glnx_throw_errno_prefix (error, "Unlocking repo failed"); |
419 | 0 | } |
420 | 0 | else if (info.state == next_state) |
421 | 0 | { |
422 | 0 | g_debug ("Maintaining lock state as %s", info.name); |
423 | 0 | } |
424 | 0 | else |
425 | 0 | { |
426 | | /* We should never drop from shared to exclusive */ |
427 | 0 | g_assert (next_state == LOCK_SH); |
428 | 0 | g_debug ("Returning lock state to shared"); |
429 | 0 | if (!do_repo_lock (self->lock.fd, next_state | flags)) |
430 | 0 | return glnx_throw_errno_prefix (error, "Setting repo lock to shared failed"); |
431 | 0 | } |
432 | | |
433 | | /* Update state */ |
434 | 0 | (*counter)--; |
435 | |
|
436 | 0 | return TRUE; |
437 | 0 | } |
438 | | static gboolean reload_config_inner (OstreeRepo *self, GCancellable *cancellable, GError **error); |
439 | | |
440 | | /** |
441 | | * ostree_repo_lock_push: |
442 | | * @self: a #OstreeRepo |
443 | | * @lock_type: the type of lock to acquire |
444 | | * @cancellable: a #GCancellable |
445 | | * @error: a #GError |
446 | | * |
447 | | * Takes a lock on the repository and adds it to the lock state. If @lock_type |
448 | | * is %OSTREE_REPO_LOCK_SHARED, a shared lock is taken. If @lock_type is |
449 | | * %OSTREE_REPO_LOCK_EXCLUSIVE, an exclusive lock is taken. The actual lock |
450 | | * state is only changed when locking a previously unlocked repository or |
451 | | * upgrading the lock from shared to exclusive. If the requested lock type is |
452 | | * unchanged or would represent a downgrade (exclusive to shared), the lock |
453 | | * state is not changed. |
454 | | * |
455 | | * ostree_repo_lock_push() waits for the lock depending on the repository's |
456 | | * lock-timeout-secs configuration. When lock-timeout-secs is -1, a blocking lock is |
457 | | * attempted. Otherwise, the lock is taken non-blocking and |
458 | | * ostree_repo_lock_push() will sleep synchronously up to lock-timeout-secs seconds |
459 | | * attempting to acquire the lock. If the lock cannot be acquired within the |
460 | | * timeout, a %G_IO_ERROR_WOULD_BLOCK error is returned. |
461 | | * |
462 | | * If @self is not writable by the user, then no locking is attempted and |
463 | | * %TRUE is returned. |
464 | | * |
465 | | * Returns: %TRUE on success, otherwise %FALSE with @error set |
466 | | * Since: 2021.3 |
467 | | */ |
468 | | gboolean |
469 | | ostree_repo_lock_push (OstreeRepo *self, OstreeRepoLockType lock_type, GCancellable *cancellable, |
470 | | GError **error) |
471 | 0 | { |
472 | 0 | g_return_val_if_fail (self != NULL, FALSE); |
473 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); |
474 | 0 | g_return_val_if_fail (self->inited, FALSE); |
475 | 0 | g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); |
476 | 0 | g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
477 | | |
478 | 0 | if (!self->writable) |
479 | 0 | return TRUE; |
480 | | |
481 | 0 | g_assert (self->lock_timeout_seconds >= REPO_LOCK_DISABLED); |
482 | 0 | if (self->lock_timeout_seconds == REPO_LOCK_DISABLED) |
483 | 0 | return TRUE; /* No locking */ |
484 | 0 | else if (self->lock_timeout_seconds == REPO_LOCK_BLOCKING) |
485 | 0 | { |
486 | 0 | g_debug ("Pushing lock blocking"); |
487 | 0 | return push_repo_lock (self, lock_type, TRUE, error); |
488 | 0 | } |
489 | 0 | else |
490 | 0 | { |
491 | | /* Convert to unsigned to guard against negative values */ |
492 | 0 | guint lock_timeout_seconds = self->lock_timeout_seconds; |
493 | 0 | guint waited = 0; |
494 | 0 | g_debug ("Pushing lock non-blocking with timeout %u", lock_timeout_seconds); |
495 | 0 | for (;;) |
496 | 0 | { |
497 | 0 | if (g_cancellable_set_error_if_cancelled (cancellable, error)) |
498 | 0 | return FALSE; |
499 | | |
500 | 0 | g_autoptr (GError) local_error = NULL; |
501 | 0 | if (push_repo_lock (self, lock_type, FALSE, &local_error)) |
502 | 0 | return TRUE; |
503 | | |
504 | 0 | if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) |
505 | 0 | { |
506 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
507 | 0 | return FALSE; |
508 | 0 | } |
509 | | |
510 | 0 | if (waited >= lock_timeout_seconds) |
511 | 0 | { |
512 | 0 | g_debug ("Push lock: Could not acquire lock within %u seconds", lock_timeout_seconds); |
513 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
514 | 0 | return FALSE; |
515 | 0 | } |
516 | | |
517 | | /* Sleep 1 second and try again */ |
518 | 0 | if (waited % 60 == 0) |
519 | 0 | { |
520 | 0 | guint remaining = lock_timeout_seconds - waited; |
521 | 0 | g_debug ("Push lock: Waiting %u more second%s to acquire lock", remaining, |
522 | 0 | (remaining == 1) ? "" : "s"); |
523 | 0 | } |
524 | 0 | waited++; |
525 | 0 | sleep (1); |
526 | 0 | } |
527 | 0 | } |
528 | 0 | } |
529 | | |
530 | | /** |
531 | | * ostree_repo_lock_pop: |
532 | | * @self: a #OstreeRepo |
533 | | * @lock_type: the type of lock to release |
534 | | * @cancellable: a #GCancellable |
535 | | * @error: a #GError |
536 | | * |
537 | | * Release a lock of type @lock_type from the lock state. If the lock state |
538 | | * becomes empty, the repository is unlocked. Otherwise, the lock state only |
539 | | * changes when transitioning from an exclusive lock back to a shared lock. The |
540 | | * requested @lock_type must be the same type that was requested in the call to |
541 | | * ostree_repo_lock_push(). It is a programmer error if these do not match and |
542 | | * the program may abort if the lock would reach an invalid state. |
543 | | * |
544 | | * ostree_repo_lock_pop() waits for the lock depending on the repository's |
545 | | * lock-timeout-secs configuration. When lock-timeout-secs is -1, a blocking lock is |
546 | | * attempted. Otherwise, the lock is removed non-blocking and |
547 | | * ostree_repo_lock_pop() will sleep synchronously up to lock-timeout-secs seconds |
548 | | * attempting to remove the lock. If the lock cannot be removed within the |
549 | | * timeout, a %G_IO_ERROR_WOULD_BLOCK error is returned. |
550 | | * |
551 | | * If @self is not writable by the user, then no unlocking is attempted and |
552 | | * %TRUE is returned. |
553 | | * |
554 | | * Returns: %TRUE on success, otherwise %FALSE with @error set |
555 | | * Since: 2021.3 |
556 | | */ |
557 | | gboolean |
558 | | ostree_repo_lock_pop (OstreeRepo *self, OstreeRepoLockType lock_type, GCancellable *cancellable, |
559 | | GError **error) |
560 | 0 | { |
561 | 0 | g_return_val_if_fail (self != NULL, FALSE); |
562 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); |
563 | 0 | g_return_val_if_fail (self->inited, FALSE); |
564 | 0 | g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); |
565 | 0 | g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
566 | | |
567 | 0 | if (!self->writable) |
568 | 0 | return TRUE; |
569 | | |
570 | 0 | g_assert (self->lock_timeout_seconds >= REPO_LOCK_DISABLED); |
571 | 0 | if (self->lock_timeout_seconds == REPO_LOCK_DISABLED) |
572 | 0 | return TRUE; |
573 | 0 | else if (self->lock_timeout_seconds == REPO_LOCK_BLOCKING) |
574 | 0 | { |
575 | 0 | g_debug ("Popping lock blocking"); |
576 | 0 | return pop_repo_lock (self, lock_type, TRUE, error); |
577 | 0 | } |
578 | 0 | else |
579 | 0 | { |
580 | | /* Convert to unsigned to guard against negative values */ |
581 | 0 | guint lock_timeout_seconds = self->lock_timeout_seconds; |
582 | 0 | guint waited = 0; |
583 | 0 | g_debug ("Popping lock non-blocking with timeout %u", lock_timeout_seconds); |
584 | 0 | for (;;) |
585 | 0 | { |
586 | 0 | if (g_cancellable_set_error_if_cancelled (cancellable, error)) |
587 | 0 | return FALSE; |
588 | | |
589 | 0 | g_autoptr (GError) local_error = NULL; |
590 | 0 | if (pop_repo_lock (self, lock_type, FALSE, &local_error)) |
591 | 0 | return TRUE; |
592 | | |
593 | 0 | if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) |
594 | 0 | { |
595 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
596 | 0 | return FALSE; |
597 | 0 | } |
598 | | |
599 | 0 | if (waited >= lock_timeout_seconds) |
600 | 0 | { |
601 | 0 | g_debug ("Pop lock: Could not remove lock within %u seconds", lock_timeout_seconds); |
602 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
603 | 0 | return FALSE; |
604 | 0 | } |
605 | | |
606 | | /* Sleep 1 second and try again */ |
607 | 0 | if (waited % 60 == 0) |
608 | 0 | { |
609 | 0 | guint remaining = lock_timeout_seconds - waited; |
610 | 0 | g_debug ("Pop lock: Waiting %u more second%s to remove lock", remaining, |
611 | 0 | (remaining == 1) ? "" : "s"); |
612 | 0 | } |
613 | 0 | waited++; |
614 | 0 | sleep (1); |
615 | 0 | } |
616 | 0 | } |
617 | 0 | } |
618 | | |
619 | | struct OstreeRepoAutoLock |
620 | | { |
621 | | OstreeRepo *repo; |
622 | | OstreeRepoLockType lock_type; |
623 | | }; |
624 | | |
625 | | /** |
626 | | * ostree_repo_auto_lock_push: (skip) |
627 | | * @self: a #OstreeRepo |
628 | | * @lock_type: the type of lock to acquire |
629 | | * @cancellable: a #GCancellable |
630 | | * @error: a #GError |
631 | | * |
632 | | * Like ostree_repo_lock_push(), but for usage with #OstreeRepoAutoLock. The |
633 | | * intended usage is to declare the #OstreeRepoAutoLock with g_autoptr() so |
634 | | * that ostree_repo_auto_lock_cleanup() is called when it goes out of scope. |
635 | | * This will automatically release the lock if it was acquired successfully. |
636 | | * |
637 | | * |[<!-- language="C" --> |
638 | | * g_autoptr(OstreeRepoAutoLock) lock = NULL; |
639 | | * lock = ostree_repo_auto_lock_push (repo, lock_type, cancellable, error); |
640 | | * if (!lock) |
641 | | * return FALSE; |
642 | | * ]| |
643 | | * |
644 | | * Returns: @self on success, otherwise %NULL with @error set |
645 | | * Since: 2021.3 |
646 | | */ |
647 | | OstreeRepoAutoLock * |
648 | | ostree_repo_auto_lock_push (OstreeRepo *self, OstreeRepoLockType lock_type, |
649 | | GCancellable *cancellable, GError **error) |
650 | 0 | { |
651 | 0 | if (!ostree_repo_lock_push (self, lock_type, cancellable, error)) |
652 | 0 | return NULL; |
653 | | |
654 | 0 | OstreeRepoAutoLock *auto_lock = g_new (OstreeRepoAutoLock, 1); |
655 | 0 | auto_lock->repo = self; |
656 | 0 | auto_lock->lock_type = lock_type; |
657 | 0 | return auto_lock; |
658 | 0 | } |
659 | | |
660 | | /** |
661 | | * ostree_repo_auto_lock_cleanup: (skip) |
662 | | * @lock: a #OstreeRepoAutoLock |
663 | | * |
664 | | * A cleanup handler for use with ostree_repo_auto_lock_push(). If @lock is |
665 | | * not %NULL, ostree_repo_lock_pop() will be called on it. If |
666 | | * ostree_repo_lock_pop() fails, a critical warning will be emitted. |
667 | | * |
668 | | * Since: 2021.3 |
669 | | */ |
670 | | void |
671 | | ostree_repo_auto_lock_cleanup (OstreeRepoAutoLock *auto_lock) |
672 | 0 | { |
673 | 0 | if (auto_lock != NULL) |
674 | 0 | { |
675 | 0 | g_autoptr (GError) error = NULL; |
676 | 0 | int errsv = errno; |
677 | |
|
678 | 0 | if (!ostree_repo_lock_pop (auto_lock->repo, auto_lock->lock_type, NULL, &error)) |
679 | 0 | g_critical ("Cleanup repo lock failed: %s", error->message); |
680 | |
|
681 | 0 | errno = errsv; |
682 | |
|
683 | 0 | g_free (auto_lock); |
684 | 0 | } |
685 | 0 | } |
686 | | |
687 | | /** |
688 | | * _ostree_repo_auto_transaction_new: |
689 | | * @repo: (not nullable): an #OsreeRepo object |
690 | | * @cancellable: Cancellable |
691 | | * @error: a #GError |
692 | | * |
693 | | * Return a guard for a transaction in @repo. |
694 | | * |
695 | | * Do not call this function outside the OstreeRepo transaction implementation. |
696 | | * Use _ostree_repo_auto_transaction_start() instead. |
697 | | * |
698 | | * Returns: (transfer full): an #OstreeRepoAutoTransaction guard on success, |
699 | | * %NULL otherwise. |
700 | | */ |
701 | | OstreeRepoAutoTransaction * |
702 | | _ostree_repo_auto_transaction_new (OstreeRepo *repo) |
703 | 0 | { |
704 | 0 | g_assert (repo != NULL); |
705 | | |
706 | 0 | OstreeRepoAutoTransaction *txn = g_malloc (sizeof (OstreeRepoAutoTransaction)); |
707 | 0 | txn->atomic_refcount = 1; |
708 | 0 | txn->repo = g_object_ref (repo); |
709 | |
|
710 | 0 | return g_steal_pointer (&txn); |
711 | 0 | } |
712 | | |
713 | | /** |
714 | | * _ostree_repo_auto_transaction_start: |
715 | | * @repo: (not nullable): an #OsreeRepo object |
716 | | * @cancellable: Cancellable |
717 | | * @error: a #GError |
718 | | * |
719 | | * Start a transaction and return a guard for it. |
720 | | * |
721 | | * Returns: (transfer full): an #OsreeRepoAutoTransaction guard on success, |
722 | | * %NULL otherwise. |
723 | | */ |
724 | | OstreeRepoAutoTransaction * |
725 | | _ostree_repo_auto_transaction_start (OstreeRepo *repo, GCancellable *cancellable, GError **error) |
726 | 0 | { |
727 | 0 | g_assert (repo != NULL); |
728 | | |
729 | 0 | if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) |
730 | 0 | return NULL; |
731 | | |
732 | 0 | return _ostree_repo_auto_transaction_new (repo); |
733 | 0 | } |
734 | | |
735 | | /** |
736 | | * _ostree_repo_auto_transaction_abort: |
737 | | * @txn: (not nullable): an #OsreeRepoAutoTransaction guard |
738 | | * @cancellable: Cancellable |
739 | | * @error: a #GError |
740 | | * |
741 | | * Abort a transaction, marking the related guard as completed. |
742 | | * |
743 | | * Returns: %TRUE on successful commit, %FALSE otherwise. |
744 | | */ |
745 | | gboolean |
746 | | _ostree_repo_auto_transaction_abort (OstreeRepoAutoTransaction *txn, GCancellable *cancellable, |
747 | | GError **error) |
748 | 0 | { |
749 | 0 | g_assert (txn != NULL); |
750 | | |
751 | 0 | if (txn->repo == NULL) |
752 | 0 | { |
753 | 0 | return glnx_throw (error, "transaction already completed"); |
754 | 0 | } |
755 | | |
756 | 0 | if (!ostree_repo_abort_transaction (txn->repo, cancellable, error)) |
757 | 0 | return FALSE; |
758 | | |
759 | 0 | g_clear_object (&txn->repo); |
760 | |
|
761 | 0 | return TRUE; |
762 | 0 | } |
763 | | |
764 | | /** |
765 | | * _ostree_repo_auto_transaction_commit: |
766 | | * @txn: (not nullable): an #OsreeRepoAutoTransaction guard |
767 | | * @out_stats: (out) (allow-none): transaction result statistics |
768 | | * @cancellable: Cancellable |
769 | | * @error: a #GError |
770 | | * |
771 | | * Commit a transaction, marking the related guard as completed. |
772 | | * |
773 | | * Returns: %TRUE on successful aborting, %FALSE otherwise. |
774 | | */ |
775 | | gboolean |
776 | | _ostree_repo_auto_transaction_commit (OstreeRepoAutoTransaction *txn, |
777 | | OstreeRepoTransactionStats *out_stats, |
778 | | GCancellable *cancellable, GError **error) |
779 | 0 | { |
780 | 0 | g_assert (txn != NULL); |
781 | | |
782 | 0 | if (txn->repo == NULL) |
783 | 0 | { |
784 | 0 | return glnx_throw (error, "transaction already completed"); |
785 | 0 | } |
786 | | |
787 | 0 | if (!ostree_repo_commit_transaction (txn->repo, out_stats, cancellable, error)) |
788 | 0 | return FALSE; |
789 | | |
790 | 0 | g_clear_object (&txn->repo); |
791 | |
|
792 | 0 | return TRUE; |
793 | 0 | } |
794 | | |
795 | | /** |
796 | | * _ostree_repo_auto_transaction_ref: |
797 | | * @txn: (not nullable): an #OsreeRepoAutoTransaction guard |
798 | | * |
799 | | * Return a new reference to the transaction guard. |
800 | | * |
801 | | * Returns: (transfer full) (not nullable): new transaction guard reference. |
802 | | */ |
803 | | OstreeRepoAutoTransaction * |
804 | | _ostree_repo_auto_transaction_ref (OstreeRepoAutoTransaction *txn) |
805 | 0 | { |
806 | 0 | g_assert (txn != NULL); |
807 | | |
808 | 0 | gint refcount = g_atomic_int_add (&txn->atomic_refcount, 1); |
809 | 0 | g_assert (refcount > 1); |
810 | | |
811 | 0 | return txn; |
812 | 0 | } |
813 | | |
814 | | /** |
815 | | * _ostree_repo_auto_transaction_unref: |
816 | | * @txn: (transfer full): an #OsreeRepoAutoTransaction guard |
817 | | * |
818 | | * Unreference a transaction guard. When the last reference is gone, |
819 | | * if the transaction has not yet been completed, it gets aborted. |
820 | | */ |
821 | | void |
822 | | _ostree_repo_auto_transaction_unref (OstreeRepoAutoTransaction *txn) |
823 | 0 | { |
824 | 0 | if (txn == NULL) |
825 | 0 | return; |
826 | | |
827 | 0 | if (!g_atomic_int_dec_and_test (&txn->atomic_refcount)) |
828 | 0 | return; |
829 | | |
830 | | // Auto-abort only if transaction has not already been aborted/committed. |
831 | 0 | if (txn->repo != NULL) |
832 | 0 | { |
833 | 0 | g_autoptr (GError) error = NULL; |
834 | 0 | if (!ostree_repo_abort_transaction (txn->repo, NULL, &error)) |
835 | 0 | g_warning ("Failed to auto-cleanup OSTree transaction: %s", error->message); |
836 | |
|
837 | 0 | g_clear_object (&txn->repo); |
838 | 0 | } |
839 | |
|
840 | 0 | g_free (txn); |
841 | 0 | return; |
842 | 0 | } |
843 | | |
844 | 0 | G_DEFINE_BOXED_TYPE (OstreeRepoAutoTransaction, _ostree_repo_auto_transaction, |
845 | 0 | _ostree_repo_auto_transaction_ref, _ostree_repo_auto_transaction_unref); |
846 | 0 |
|
847 | 0 | static GFile *get_remotes_d_dir (OstreeRepo *self, GFile *sysroot); |
848 | 0 |
|
849 | 0 | OstreeRemote * |
850 | 0 | _ostree_repo_get_remote (OstreeRepo *self, const char *name, GError **error) |
851 | 7 | { |
852 | 7 | OstreeRemote *remote = NULL; |
853 | | |
854 | 7 | g_return_val_if_fail (name != NULL, NULL); |
855 | | |
856 | 7 | g_mutex_lock (&self->remotes_lock); |
857 | | |
858 | 7 | remote = g_hash_table_lookup (self->remotes, name); |
859 | | |
860 | 7 | if (remote != NULL) |
861 | 0 | ostree_remote_ref (remote); |
862 | 7 | else |
863 | 7 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Remote \"%s\" not found", name); |
864 | | |
865 | 7 | g_mutex_unlock (&self->remotes_lock); |
866 | | |
867 | 7 | return remote; |
868 | 7 | } |
869 | | |
870 | | OstreeRemote * |
871 | | _ostree_repo_get_remote_inherited (OstreeRepo *self, const char *name, GError **error) |
872 | 0 | { |
873 | 0 | g_autoptr (OstreeRemote) remote = NULL; |
874 | 0 | g_autoptr (GError) temp_error = NULL; |
875 | |
|
876 | 0 | remote = _ostree_repo_get_remote (self, name, &temp_error); |
877 | 0 | if (remote == NULL) |
878 | 0 | { |
879 | 0 | if (self->parent_repo != NULL) |
880 | 0 | return _ostree_repo_get_remote_inherited (self->parent_repo, name, error); |
881 | | |
882 | 0 | g_propagate_error (error, g_steal_pointer (&temp_error)); |
883 | 0 | return NULL; |
884 | 0 | } |
885 | | |
886 | 0 | return g_steal_pointer (&remote); |
887 | 0 | } |
888 | | |
889 | | gboolean |
890 | | _ostree_repo_add_remote (OstreeRepo *self, OstreeRemote *remote) |
891 | 0 | { |
892 | 0 | gboolean already_existed; |
893 | |
|
894 | 0 | g_return_val_if_fail (self != NULL, FALSE); |
895 | 0 | g_return_val_if_fail (remote != NULL, FALSE); |
896 | 0 | g_return_val_if_fail (remote->name != NULL, FALSE); |
897 | | |
898 | 0 | g_mutex_lock (&self->remotes_lock); |
899 | |
|
900 | 0 | already_existed = !g_hash_table_replace (self->remotes, remote->name, ostree_remote_ref (remote)); |
901 | |
|
902 | 0 | g_mutex_unlock (&self->remotes_lock); |
903 | |
|
904 | 0 | return already_existed; |
905 | 0 | } |
906 | | |
907 | | gboolean |
908 | | _ostree_repo_remove_remote (OstreeRepo *self, OstreeRemote *remote) |
909 | 0 | { |
910 | 0 | gboolean removed; |
911 | |
|
912 | 0 | g_return_val_if_fail (self != NULL, FALSE); |
913 | 0 | g_return_val_if_fail (remote != NULL, FALSE); |
914 | 0 | g_return_val_if_fail (remote->name != NULL, FALSE); |
915 | | |
916 | 0 | g_mutex_lock (&self->remotes_lock); |
917 | |
|
918 | 0 | removed = g_hash_table_remove (self->remotes, remote->name); |
919 | |
|
920 | 0 | g_mutex_unlock (&self->remotes_lock); |
921 | |
|
922 | 0 | return removed; |
923 | 0 | } |
924 | | |
925 | | gboolean |
926 | | _ostree_repo_remote_name_is_file (const char *remote_name) |
927 | 0 | { |
928 | 0 | return g_str_has_prefix (remote_name, "file://"); |
929 | 0 | } |
930 | | |
931 | | /** |
932 | | * ostree_repo_get_remote_option: |
933 | | * @self: A OstreeRepo |
934 | | * @remote_name: Name |
935 | | * @option_name: Option |
936 | | * @default_value: (nullable): Value returned if @option_name is not present |
937 | | * @out_value: (out) (nullable): Return location for value |
938 | | * @error: Error |
939 | | * |
940 | | * OSTree remotes are represented by keyfile groups, formatted like: |
941 | | * `[remote "remotename"]`. This function returns a value named @option_name |
942 | | * underneath that group, or @default_value if the remote exists but not the |
943 | | * option name. If an error is returned, @out_value will be set to %NULL. |
944 | | * |
945 | | * Returns: %TRUE on success, otherwise %FALSE with @error set |
946 | | * |
947 | | * Since: 2016.5 |
948 | | */ |
949 | | gboolean |
950 | | ostree_repo_get_remote_option (OstreeRepo *self, const char *remote_name, const char *option_name, |
951 | | const char *default_value, char **out_value, GError **error) |
952 | 0 | { |
953 | 0 | g_autoptr (OstreeRemote) remote = NULL; |
954 | 0 | gboolean ret = FALSE; |
955 | 0 | g_autoptr (GError) temp_error = NULL; |
956 | 0 | g_autofree char *value = NULL; |
957 | |
|
958 | 0 | if (_ostree_repo_remote_name_is_file (remote_name)) |
959 | 0 | { |
960 | 0 | *out_value = g_strdup (default_value); |
961 | 0 | return TRUE; |
962 | 0 | } |
963 | | |
964 | 0 | remote = _ostree_repo_get_remote (self, remote_name, &temp_error); |
965 | 0 | if (remote != NULL) |
966 | 0 | { |
967 | 0 | value = g_key_file_get_string (remote->options, remote->group, option_name, &temp_error); |
968 | 0 | if (value == NULL) |
969 | 0 | { |
970 | 0 | if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) |
971 | 0 | { |
972 | | /* Note: We ignore errors on the parent because the parent config may not |
973 | | specify this remote, causing a "remote not found" error, but we found |
974 | | the remote at some point, so we need to instead return the default */ |
975 | 0 | if (self->parent_repo != NULL |
976 | 0 | && ostree_repo_get_remote_option (self->parent_repo, remote_name, option_name, |
977 | 0 | default_value, out_value, NULL)) |
978 | 0 | return TRUE; |
979 | | |
980 | 0 | value = g_strdup (default_value); |
981 | 0 | ret = TRUE; |
982 | 0 | } |
983 | 0 | else |
984 | 0 | g_propagate_error (error, g_steal_pointer (&temp_error)); |
985 | 0 | } |
986 | 0 | else |
987 | 0 | ret = TRUE; |
988 | 0 | } |
989 | 0 | else if (self->parent_repo != NULL) |
990 | 0 | return ostree_repo_get_remote_option (self->parent_repo, remote_name, option_name, |
991 | 0 | default_value, out_value, error); |
992 | 0 | else |
993 | 0 | g_propagate_error (error, g_steal_pointer (&temp_error)); |
994 | | |
995 | 0 | *out_value = g_steal_pointer (&value); |
996 | 0 | return ret; |
997 | 0 | } |
998 | | |
999 | | /** |
1000 | | * ostree_repo_get_remote_list_option: |
1001 | | * @self: A OstreeRepo |
1002 | | * @remote_name: Name |
1003 | | * @option_name: Option |
1004 | | * @out_value: (out) (array zero-terminated=1): location to store the list |
1005 | | * of strings. The list should be freed with |
1006 | | * g_strfreev(). |
1007 | | * @error: Error |
1008 | | * |
1009 | | * OSTree remotes are represented by keyfile groups, formatted like: |
1010 | | * `[remote "remotename"]`. This function returns a value named @option_name |
1011 | | * underneath that group, and returns it as a zero terminated array of strings. |
1012 | | * If the option is not set, or if an error is returned, @out_value will be set |
1013 | | * to %NULL. |
1014 | | * |
1015 | | * Returns: %TRUE on success, otherwise %FALSE with @error set |
1016 | | * |
1017 | | * Since: 2016.5 |
1018 | | */ |
1019 | | gboolean |
1020 | | ostree_repo_get_remote_list_option (OstreeRepo *self, const char *remote_name, |
1021 | | const char *option_name, char ***out_value, GError **error) |
1022 | 0 | { |
1023 | 0 | g_autoptr (OstreeRemote) remote = NULL; |
1024 | 0 | gboolean ret = FALSE; |
1025 | 0 | g_autoptr (GError) temp_error = NULL; |
1026 | 0 | g_auto (GStrv) value = NULL; |
1027 | |
|
1028 | 0 | if (_ostree_repo_remote_name_is_file (remote_name)) |
1029 | 0 | { |
1030 | 0 | *out_value = NULL; |
1031 | 0 | return TRUE; |
1032 | 0 | } |
1033 | | |
1034 | 0 | remote = _ostree_repo_get_remote (self, remote_name, &temp_error); |
1035 | 0 | if (remote != NULL) |
1036 | 0 | { |
1037 | 0 | value = g_key_file_get_string_list (remote->options, remote->group, option_name, NULL, |
1038 | 0 | &temp_error); |
1039 | | |
1040 | | /* Default value if key not found is always NULL. */ |
1041 | 0 | if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) |
1042 | 0 | { |
1043 | | /* Note: We ignore errors on the parent because the parent config may not |
1044 | | specify this remote, causing a "remote not found" error, but we found |
1045 | | the remote at some point, so we need to instead return the default */ |
1046 | 0 | if (self->parent_repo != NULL |
1047 | 0 | && ostree_repo_get_remote_list_option (self->parent_repo, remote_name, option_name, |
1048 | 0 | out_value, NULL)) |
1049 | 0 | return TRUE; |
1050 | | |
1051 | 0 | ret = TRUE; |
1052 | 0 | } |
1053 | 0 | else if (temp_error) |
1054 | 0 | g_propagate_error (error, g_steal_pointer (&temp_error)); |
1055 | 0 | else |
1056 | 0 | ret = TRUE; |
1057 | 0 | } |
1058 | 0 | else if (self->parent_repo != NULL) |
1059 | 0 | return ostree_repo_get_remote_list_option (self->parent_repo, remote_name, option_name, |
1060 | 0 | out_value, error); |
1061 | 0 | else |
1062 | 0 | g_propagate_error (error, g_steal_pointer (&temp_error)); |
1063 | | |
1064 | 0 | *out_value = g_steal_pointer (&value); |
1065 | 0 | return ret; |
1066 | 0 | } |
1067 | | |
1068 | | /** |
1069 | | * ostree_repo_get_remote_boolean_option: |
1070 | | * @self: A OstreeRepo |
1071 | | * @remote_name: Name |
1072 | | * @option_name: Option |
1073 | | * @default_value: Value returned if @option_name is not present |
1074 | | * @out_value: (out) : location to store the result. |
1075 | | * @error: Error |
1076 | | * |
1077 | | * OSTree remotes are represented by keyfile groups, formatted like: |
1078 | | * `[remote "remotename"]`. This function returns a value named @option_name |
1079 | | * underneath that group, and returns it as a boolean. |
1080 | | * If the option is not set, @out_value will be set to @default_value. If an |
1081 | | * error is returned, @out_value will be set to %FALSE. |
1082 | | * |
1083 | | * Returns: %TRUE on success, otherwise %FALSE with @error set |
1084 | | * |
1085 | | * Since: 2016.5 |
1086 | | */ |
1087 | | gboolean |
1088 | | ostree_repo_get_remote_boolean_option (OstreeRepo *self, const char *remote_name, |
1089 | | const char *option_name, gboolean default_value, |
1090 | | gboolean *out_value, GError **error) |
1091 | 0 | { |
1092 | 0 | g_autoptr (OstreeRemote) remote = NULL; |
1093 | 0 | g_autoptr (GError) temp_error = NULL; |
1094 | 0 | gboolean ret = FALSE; |
1095 | 0 | gboolean value = FALSE; |
1096 | |
|
1097 | 0 | if (_ostree_repo_remote_name_is_file (remote_name)) |
1098 | 0 | { |
1099 | 0 | *out_value = default_value; |
1100 | 0 | return TRUE; |
1101 | 0 | } |
1102 | | |
1103 | 0 | remote = _ostree_repo_get_remote (self, remote_name, &temp_error); |
1104 | 0 | if (remote != NULL) |
1105 | 0 | { |
1106 | 0 | value = g_key_file_get_boolean (remote->options, remote->group, option_name, &temp_error); |
1107 | 0 | if (temp_error != NULL) |
1108 | 0 | { |
1109 | 0 | if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) |
1110 | 0 | { |
1111 | | /* Note: We ignore errors on the parent because the parent config may not |
1112 | | specify this remote, causing a "remote not found" error, but we found |
1113 | | the remote at some point, so we need to instead return the default */ |
1114 | 0 | if (self->parent_repo != NULL |
1115 | 0 | && ostree_repo_get_remote_boolean_option ( |
1116 | 0 | self->parent_repo, remote_name, option_name, default_value, out_value, NULL)) |
1117 | 0 | return TRUE; |
1118 | | |
1119 | 0 | value = default_value; |
1120 | 0 | ret = TRUE; |
1121 | 0 | } |
1122 | 0 | else |
1123 | 0 | g_propagate_error (error, g_steal_pointer (&temp_error)); |
1124 | 0 | } |
1125 | 0 | else |
1126 | 0 | ret = TRUE; |
1127 | 0 | } |
1128 | 0 | else if (self->parent_repo != NULL) |
1129 | 0 | return ostree_repo_get_remote_boolean_option (self->parent_repo, remote_name, option_name, |
1130 | 0 | default_value, out_value, error); |
1131 | 0 | else |
1132 | 0 | g_propagate_error (error, g_steal_pointer (&temp_error)); |
1133 | | |
1134 | 0 | *out_value = value; |
1135 | 0 | return ret; |
1136 | 0 | } |
1137 | | |
1138 | | static void |
1139 | | ostree_repo_finalize (GObject *object) |
1140 | 139 | { |
1141 | 139 | OstreeRepo *self = OSTREE_REPO (object); |
1142 | | |
1143 | 139 | g_clear_object (&self->parent_repo); |
1144 | | |
1145 | 139 | g_free (self->stagedir_prefix); |
1146 | 139 | g_clear_object (&self->repodir_fdrel); |
1147 | 139 | g_clear_object (&self->repodir); |
1148 | 139 | glnx_close_fd (&self->repo_dir_fd); |
1149 | 139 | glnx_tmpdir_unset (&self->commit_stagedir); |
1150 | 139 | glnx_release_lock_file (&self->commit_stagedir_lock); |
1151 | 139 | glnx_close_fd (&self->tmp_dir_fd); |
1152 | 139 | glnx_close_fd (&self->cache_dir_fd); |
1153 | 139 | glnx_close_fd (&self->objects_dir_fd); |
1154 | 139 | glnx_close_fd (&self->uncompressed_objects_dir_fd); |
1155 | 139 | g_clear_object (&self->sysroot_dir); |
1156 | 139 | g_weak_ref_clear (&self->sysroot); |
1157 | 139 | g_free (self->remotes_config_dir); |
1158 | | |
1159 | 139 | if (self->loose_object_devino_hash) |
1160 | 0 | g_hash_table_destroy (self->loose_object_devino_hash); |
1161 | 139 | if (self->updated_uncompressed_dirs) |
1162 | 0 | g_hash_table_destroy (self->updated_uncompressed_dirs); |
1163 | 139 | if (self->config) |
1164 | 139 | g_key_file_free (self->config); |
1165 | 139 | g_clear_pointer (&self->txn.refs, g_hash_table_destroy); |
1166 | 139 | g_clear_pointer (&self->txn.collection_refs, g_hash_table_destroy); |
1167 | 139 | g_clear_error (&self->writable_error); |
1168 | 139 | g_clear_pointer (&self->object_sizes, g_hash_table_unref); |
1169 | 139 | g_clear_pointer (&self->dirmeta_cache, g_hash_table_unref); |
1170 | 139 | g_mutex_clear (&self->cache_lock); |
1171 | 139 | g_mutex_clear (&self->txn_lock); |
1172 | 139 | g_free (self->collection_id); |
1173 | 139 | g_strfreev (self->repo_finders); |
1174 | 139 | g_clear_pointer (&self->bls_append_values, g_hash_table_unref); |
1175 | | |
1176 | 139 | g_clear_pointer (&self->remotes, g_hash_table_destroy); |
1177 | 139 | g_mutex_clear (&self->remotes_lock); |
1178 | | |
1179 | 139 | glnx_close_fd (&self->lock.fd); |
1180 | 139 | g_mutex_clear (&self->lock.mutex); |
1181 | | |
1182 | 139 | G_OBJECT_CLASS (ostree_repo_parent_class)->finalize (object); |
1183 | 139 | } |
1184 | | |
1185 | | static void |
1186 | | ostree_repo_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) |
1187 | 417 | { |
1188 | 417 | OstreeRepo *self = OSTREE_REPO (object); |
1189 | | |
1190 | 417 | switch (prop_id) |
1191 | 417 | { |
1192 | 139 | case PROP_PATH: |
1193 | 139 | self->repodir = g_value_dup_object (value); |
1194 | 139 | break; |
1195 | 139 | case PROP_SYSROOT_PATH: |
1196 | 139 | self->sysroot_dir = g_value_dup_object (value); |
1197 | 139 | break; |
1198 | 139 | case PROP_REMOTES_CONFIG_DIR: |
1199 | 139 | self->remotes_config_dir = g_value_dup_string (value); |
1200 | 139 | break; |
1201 | 0 | default: |
1202 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
1203 | 0 | break; |
1204 | 417 | } |
1205 | 417 | } |
1206 | | |
1207 | | static void |
1208 | | ostree_repo_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) |
1209 | 0 | { |
1210 | 0 | OstreeRepo *self = OSTREE_REPO (object); |
1211 | |
|
1212 | 0 | switch (prop_id) |
1213 | 0 | { |
1214 | 0 | case PROP_PATH: |
1215 | 0 | g_value_set_object (value, self->repodir); |
1216 | 0 | break; |
1217 | 0 | case PROP_SYSROOT_PATH: |
1218 | 0 | g_value_set_object (value, self->sysroot_dir); |
1219 | 0 | break; |
1220 | 0 | case PROP_REMOTES_CONFIG_DIR: |
1221 | 0 | g_value_set_string (value, self->remotes_config_dir); |
1222 | 0 | break; |
1223 | 0 | default: |
1224 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
1225 | 0 | break; |
1226 | 0 | } |
1227 | 0 | } |
1228 | | |
1229 | | static void |
1230 | | ostree_repo_class_init (OstreeRepoClass *klass) |
1231 | 1 | { |
1232 | 1 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
1233 | | |
1234 | 1 | object_class->get_property = ostree_repo_get_property; |
1235 | 1 | object_class->set_property = ostree_repo_set_property; |
1236 | 1 | object_class->finalize = ostree_repo_finalize; |
1237 | | |
1238 | | /** |
1239 | | * OstreeRepo:path: |
1240 | | * |
1241 | | * Path to repository. Note that if this repository was created |
1242 | | * via `ostree_repo_new_at()`, this value will refer to a value in |
1243 | | * the Linux kernel's `/proc/self/fd` directory. Generally, you |
1244 | | * should avoid using this property at all; you can gain a reference |
1245 | | * to the repository's directory fd via `ostree_repo_get_dfd()` and |
1246 | | * use file-descriptor relative operations. |
1247 | | */ |
1248 | 1 | g_object_class_install_property ( |
1249 | 1 | object_class, PROP_PATH, |
1250 | 1 | g_param_spec_object ("path", "Path", "Path", G_TYPE_FILE, |
1251 | 1 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); |
1252 | | /** |
1253 | | * OstreeRepo:sysroot-path: |
1254 | | * |
1255 | | * A system using libostree for the host has a "system" repository; this |
1256 | | * property will be set for repositories referenced via |
1257 | | * `ostree_sysroot_repo()` for example. |
1258 | | * |
1259 | | * You should avoid using this property; if your code is operating |
1260 | | * on a system repository, use `OstreeSysroot` and access the repository |
1261 | | * object via `ostree_sysroot_repo()`. |
1262 | | */ |
1263 | 1 | g_object_class_install_property ( |
1264 | 1 | object_class, PROP_SYSROOT_PATH, |
1265 | 1 | g_param_spec_object ("sysroot-path", "", "", G_TYPE_FILE, |
1266 | 1 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); |
1267 | | /** |
1268 | | * OstreeRepo:remotes-config-dir: |
1269 | | * |
1270 | | * Path to directory containing remote definitions. The default is `NULL`. |
1271 | | * If a `sysroot-path` property is defined, this value will default to |
1272 | | * `${sysroot_path}/etc/ostree/remotes.d`. |
1273 | | * |
1274 | | * This value will only be used for system repositories. |
1275 | | */ |
1276 | 1 | g_object_class_install_property ( |
1277 | 1 | object_class, PROP_REMOTES_CONFIG_DIR, |
1278 | 1 | g_param_spec_string ("remotes-config-dir", "", "", NULL, |
1279 | 1 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); |
1280 | | |
1281 | 1 | #ifndef OSTREE_DISABLE_GPGME |
1282 | | /** |
1283 | | * OstreeRepo::gpg-verify-result: |
1284 | | * @self: an #OstreeRepo |
1285 | | * @checksum: checksum of the signed object |
1286 | | * @result: an #OstreeGpgVerifyResult |
1287 | | * |
1288 | | * Emitted during a pull operation upon GPG verification (if enabled). |
1289 | | * Applications can connect to this signal to output the verification |
1290 | | * results if desired. |
1291 | | * |
1292 | | * The signal will be emitted from whichever #GMainContext is the |
1293 | | * thread-default at the point when ostree_repo_pull_with_options() |
1294 | | * is called. |
1295 | | */ |
1296 | 1 | signals[GPG_VERIFY_RESULT] |
1297 | 1 | = g_signal_new ("gpg-verify-result", OSTREE_TYPE_REPO, G_SIGNAL_RUN_LAST, |
1298 | 1 | G_STRUCT_OFFSET (OstreeRepoClass, gpg_verify_result), NULL, NULL, NULL, |
1299 | 1 | G_TYPE_NONE, 2, G_TYPE_STRING, OSTREE_TYPE_GPG_VERIFY_RESULT); |
1300 | 1 | #endif /* OSTREE_DISABLE_GPGME */ |
1301 | 1 | } |
1302 | | |
1303 | | static void |
1304 | | ostree_repo_init (OstreeRepo *self) |
1305 | 139 | { |
1306 | 139 | const GDebugKey test_error_keys[] = { |
1307 | 139 | { "pre-commit", OSTREE_REPO_TEST_ERROR_PRE_COMMIT }, |
1308 | 139 | { "invalid-cache", OSTREE_REPO_TEST_ERROR_INVALID_CACHE }, |
1309 | 139 | }; |
1310 | | |
1311 | 139 | #ifndef OSTREE_DISABLE_GPGME |
1312 | 139 | static gsize gpgme_initialized; |
1313 | | |
1314 | 139 | if (g_once_init_enter (&gpgme_initialized)) |
1315 | 1 | { |
1316 | 1 | gpgme_check_version (NULL); |
1317 | 1 | gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL)); |
1318 | 1 | g_once_init_leave (&gpgme_initialized, 1); |
1319 | 1 | } |
1320 | 139 | #endif |
1321 | | |
1322 | 139 | self->test_error_flags = g_parse_debug_string (g_getenv ("OSTREE_REPO_TEST_ERROR"), |
1323 | 139 | test_error_keys, G_N_ELEMENTS (test_error_keys)); |
1324 | | |
1325 | 139 | g_mutex_init (&self->lock.mutex); |
1326 | 139 | g_mutex_init (&self->cache_lock); |
1327 | 139 | g_mutex_init (&self->txn_lock); |
1328 | | |
1329 | 139 | self->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)NULL, |
1330 | 139 | (GDestroyNotify)ostree_remote_unref); |
1331 | 139 | self->bls_append_values = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, |
1332 | 139 | (GDestroyNotify)g_free); |
1333 | 139 | g_mutex_init (&self->remotes_lock); |
1334 | | |
1335 | 139 | self->repo_dir_fd = -1; |
1336 | 139 | self->cache_dir_fd = -1; |
1337 | 139 | self->tmp_dir_fd = -1; |
1338 | 139 | self->objects_dir_fd = -1; |
1339 | 139 | self->uncompressed_objects_dir_fd = -1; |
1340 | 139 | self->lock.fd = -1; |
1341 | 139 | self->sysroot_kind = OSTREE_REPO_SYSROOT_KIND_UNKNOWN; |
1342 | 139 | } |
1343 | | |
1344 | | /** |
1345 | | * ostree_repo_new: |
1346 | | * @path: Path to a repository |
1347 | | * |
1348 | | * Returns: (transfer full): An accessor object for an OSTree repository located at @path |
1349 | | */ |
1350 | | OstreeRepo * |
1351 | | ostree_repo_new (GFile *path) |
1352 | 0 | { |
1353 | 0 | return g_object_new (OSTREE_TYPE_REPO, "path", path, NULL); |
1354 | 0 | } |
1355 | | |
1356 | | static OstreeRepo * |
1357 | | repo_open_at_take_fd (int *dfd, GCancellable *cancellable, GError **error) |
1358 | 139 | { |
1359 | 139 | g_autoptr (OstreeRepo) repo = g_object_new (OSTREE_TYPE_REPO, NULL); |
1360 | 139 | repo->repo_dir_fd = g_steal_fd (dfd); |
1361 | | |
1362 | 139 | if (!ostree_repo_open (repo, cancellable, error)) |
1363 | 0 | return NULL; |
1364 | 139 | return g_steal_pointer (&repo); |
1365 | 139 | } |
1366 | | |
1367 | | /** |
1368 | | * ostree_repo_open_at: |
1369 | | * @dfd: Directory fd |
1370 | | * @path: Path |
1371 | | * |
1372 | | * This combines ostree_repo_new() (but using fd-relative access) with |
1373 | | * ostree_repo_open(). Use this when you know you should be operating on an |
1374 | | * already extant repository. If you want to create one, use ostree_repo_create_at(). |
1375 | | * |
1376 | | * Returns: (transfer full): An accessor object for an OSTree repository located at @dfd + @path |
1377 | | * |
1378 | | * Since: 2017.10 |
1379 | | */ |
1380 | | OstreeRepo * |
1381 | | ostree_repo_open_at (int dfd, const char *path, GCancellable *cancellable, GError **error) |
1382 | 0 | { |
1383 | 0 | glnx_autofd int repo_dfd = -1; |
1384 | 0 | if (!glnx_opendirat (dfd, path, TRUE, &repo_dfd, error)) |
1385 | 0 | return NULL; |
1386 | | |
1387 | 0 | return repo_open_at_take_fd (&repo_dfd, cancellable, error); |
1388 | 0 | } |
1389 | | |
1390 | | static GFile * |
1391 | | get_default_repo_path (GFile *sysroot_path) |
1392 | 0 | { |
1393 | 0 | if (sysroot_path == NULL) |
1394 | 0 | sysroot_path = _ostree_get_default_sysroot_path (); |
1395 | |
|
1396 | 0 | return g_file_resolve_relative_path (sysroot_path, "ostree/repo"); |
1397 | 0 | } |
1398 | | |
1399 | | /** |
1400 | | * ostree_repo_new_for_sysroot_path: |
1401 | | * @repo_path: Path to a repository |
1402 | | * @sysroot_path: Path to the system root |
1403 | | * |
1404 | | * Creates a new #OstreeRepo instance, taking the system root path explicitly |
1405 | | * instead of assuming "/". |
1406 | | * |
1407 | | * Returns: (transfer full): An accessor object for the OSTree repository located at @repo_path. |
1408 | | */ |
1409 | | OstreeRepo * |
1410 | | ostree_repo_new_for_sysroot_path (GFile *repo_path, GFile *sysroot_path) |
1411 | 0 | { |
1412 | 0 | return g_object_new (OSTREE_TYPE_REPO, "path", repo_path, "sysroot-path", sysroot_path, NULL); |
1413 | 0 | } |
1414 | | |
1415 | | /** |
1416 | | * ostree_repo_new_default: |
1417 | | * |
1418 | | * If the current working directory appears to be an OSTree |
1419 | | * repository, create a new #OstreeRepo object for accessing it. |
1420 | | * Otherwise use the path in the OSTREE_REPO environment variable |
1421 | | * (if defined) or else the default system repository located at |
1422 | | * /ostree/repo. |
1423 | | * |
1424 | | * Returns: (transfer full): An accessor object for an OSTree repository located at /ostree/repo |
1425 | | */ |
1426 | | OstreeRepo * |
1427 | | ostree_repo_new_default (void) |
1428 | 0 | { |
1429 | 0 | if (g_file_test ("objects", G_FILE_TEST_IS_DIR) && g_file_test ("config", G_FILE_TEST_IS_REGULAR)) |
1430 | 0 | { |
1431 | 0 | g_autoptr (GFile) cwd = g_file_new_for_path ("."); |
1432 | 0 | return ostree_repo_new (cwd); |
1433 | 0 | } |
1434 | 0 | else |
1435 | 0 | { |
1436 | 0 | const char *envvar = g_getenv ("OSTREE_REPO"); |
1437 | 0 | g_autoptr (GFile) repo_path = NULL; |
1438 | |
|
1439 | 0 | if (envvar == NULL || *envvar == '\0') |
1440 | 0 | repo_path = get_default_repo_path (NULL); |
1441 | 0 | else |
1442 | 0 | repo_path = g_file_new_for_path (envvar); |
1443 | |
|
1444 | 0 | return ostree_repo_new (repo_path); |
1445 | 0 | } |
1446 | 0 | } |
1447 | | |
1448 | | /** |
1449 | | * ostree_repo_is_system: |
1450 | | * @repo: Repository |
1451 | | * |
1452 | | * Returns: %TRUE if this repository is the root-owned system global repository |
1453 | | */ |
1454 | | gboolean |
1455 | | ostree_repo_is_system (OstreeRepo *repo) |
1456 | 228 | { |
1457 | 228 | g_return_val_if_fail (OSTREE_IS_REPO (repo), FALSE); |
1458 | | |
1459 | | /* If we were created via ostree_sysroot_get_repo(), we know the answer is yes |
1460 | | * without having to compare file paths. |
1461 | | */ |
1462 | 228 | if (repo->sysroot_kind == OSTREE_REPO_SYSROOT_KIND_VIA_SYSROOT |
1463 | 228 | || repo->sysroot_kind == OSTREE_REPO_SYSROOT_KIND_IS_SYSROOT_OSTREE) |
1464 | 0 | return TRUE; |
1465 | | |
1466 | | /* No sysroot_dir set? Not a system repo then. */ |
1467 | 228 | if (!repo->sysroot_dir) |
1468 | 228 | return FALSE; |
1469 | | |
1470 | | /* If we created via ostree_repo_new(), we'll have a repo path. Compare |
1471 | | * it to the sysroot path in that case. |
1472 | | */ |
1473 | 0 | if (repo->repodir) |
1474 | 0 | { |
1475 | 0 | g_autoptr (GFile) default_repo_path = get_default_repo_path (repo->sysroot_dir); |
1476 | 0 | return g_file_equal (repo->repodir, default_repo_path); |
1477 | 0 | } |
1478 | | /* Otherwise, not a system repo */ |
1479 | 0 | return FALSE; |
1480 | 0 | } |
1481 | | |
1482 | | /** |
1483 | | * ostree_repo_is_writable: |
1484 | | * @self: Repo |
1485 | | * @error: a #GError |
1486 | | * |
1487 | | * Returns whether the repository is writable by the current user. |
1488 | | * If the repository is not writable, the @error indicates why. |
1489 | | * |
1490 | | * Returns: %TRUE if this repository is writable |
1491 | | */ |
1492 | | gboolean |
1493 | | ostree_repo_is_writable (OstreeRepo *self, GError **error) |
1494 | 0 | { |
1495 | 0 | g_assert (self != NULL); |
1496 | 0 | g_assert (self->inited); |
1497 | | |
1498 | 0 | g_assert (self->writable == (self->writable_error == NULL)); |
1499 | 0 | if (error != NULL && self->writable_error != NULL) |
1500 | 0 | *error = g_error_copy (self->writable_error); |
1501 | |
|
1502 | 0 | return self->writable; |
1503 | 0 | } |
1504 | | |
1505 | | /** |
1506 | | * _ostree_repo_update_mtime: |
1507 | | * @self: Repo |
1508 | | * @error: a #GError |
1509 | | * |
1510 | | * Bump the mtime of the repository so that programs |
1511 | | * can detect that the refs have updated. |
1512 | | */ |
1513 | | gboolean |
1514 | | _ostree_repo_update_mtime (OstreeRepo *self, GError **error) |
1515 | 0 | { |
1516 | 0 | if (futimens (self->repo_dir_fd, NULL) != 0) |
1517 | 0 | { |
1518 | 0 | glnx_set_prefix_error_from_errno (error, "%s", "futimens"); |
1519 | 0 | return FALSE; |
1520 | 0 | } |
1521 | 0 | return TRUE; |
1522 | 0 | } |
1523 | | |
1524 | | gboolean |
1525 | | _ostree_repo_syncfs (OstreeRepo *self, GError **error) |
1526 | 0 | { |
1527 | |
|
1528 | 0 | if (self->disable_fsync) |
1529 | 0 | return TRUE; |
1530 | | |
1531 | 0 | gboolean is_system = ostree_repo_is_system (self); |
1532 | 0 | if (is_system) |
1533 | 0 | ot_journal_print (LOG_INFO, "Starting syncfs for system repo"); |
1534 | 0 | guint64 start_msec = g_get_monotonic_time () / 1000; |
1535 | 0 | int repo_dfd = ostree_repo_get_dfd (self); |
1536 | 0 | if (syncfs (repo_dfd) != 0) |
1537 | 0 | return glnx_throw_errno_prefix (error, "syncfs(repo)"); |
1538 | 0 | guint64 end_msec = g_get_monotonic_time () / 1000; |
1539 | 0 | if (is_system) |
1540 | 0 | ot_journal_print (LOG_INFO, "Completed syncfs() for system repo in %" G_GUINT64_FORMAT " ms", |
1541 | 0 | end_msec - start_msec); |
1542 | 0 | return TRUE; |
1543 | 0 | } |
1544 | | |
1545 | | /** |
1546 | | * ostree_repo_get_config: |
1547 | | * @self: |
1548 | | * |
1549 | | * Returns: (transfer none): The repository configuration; do not modify |
1550 | | */ |
1551 | | GKeyFile * |
1552 | | ostree_repo_get_config (OstreeRepo *self) |
1553 | 0 | { |
1554 | 0 | g_assert (self != NULL); |
1555 | 0 | g_assert (self->inited); |
1556 | | |
1557 | 0 | return self->config; |
1558 | 0 | } |
1559 | | |
1560 | | /** |
1561 | | * ostree_repo_copy_config: |
1562 | | * @self: |
1563 | | * |
1564 | | * Returns: (transfer full): A newly-allocated copy of the repository config |
1565 | | */ |
1566 | | GKeyFile * |
1567 | | ostree_repo_copy_config (OstreeRepo *self) |
1568 | 139 | { |
1569 | 139 | GKeyFile *copy; |
1570 | 139 | char *data; |
1571 | 139 | gsize len; |
1572 | | |
1573 | 139 | g_assert (self != NULL); |
1574 | 139 | g_assert (self->inited); |
1575 | | |
1576 | 139 | copy = g_key_file_new (); |
1577 | 139 | data = g_key_file_to_data (self->config, &len, NULL); |
1578 | 139 | if (!g_key_file_load_from_data (copy, data, len, 0, NULL)) |
1579 | 139 | g_assert_not_reached (); |
1580 | 139 | g_free (data); |
1581 | 139 | return copy; |
1582 | 139 | } |
1583 | | |
1584 | | /** |
1585 | | * ostree_repo_write_config: |
1586 | | * @self: Repo |
1587 | | * @new_config: Overwrite the config file with this data |
1588 | | * @error: a #GError |
1589 | | * |
1590 | | * Save @new_config in place of this repository's config file. |
1591 | | * |
1592 | | * Note: This will not validate many elements of the configuration. |
1593 | | * Prefer `ostree_repo_write_config_and_reload`. |
1594 | | */ |
1595 | | gboolean |
1596 | | ostree_repo_write_config (OstreeRepo *self, GKeyFile *new_config, GError **error) |
1597 | 139 | { |
1598 | 139 | g_return_val_if_fail (self->inited, FALSE); |
1599 | | |
1600 | | /* Ensure that any remotes in the new config aren't defined in a |
1601 | | * separate config file. |
1602 | | */ |
1603 | 139 | gsize num_groups; |
1604 | 139 | g_auto (GStrv) groups = g_key_file_get_groups (new_config, &num_groups); |
1605 | 414 | for (gsize i = 0; i < num_groups; i++) |
1606 | 275 | { |
1607 | 275 | g_autoptr (OstreeRemote) new_remote = ostree_remote_new_from_keyfile (new_config, groups[i]); |
1608 | 275 | if (new_remote != NULL) |
1609 | 7 | { |
1610 | 7 | g_autoptr (GError) local_error = NULL; |
1611 | | |
1612 | 7 | g_autoptr (OstreeRemote) cur_remote |
1613 | 7 | = _ostree_repo_get_remote (self, new_remote->name, &local_error); |
1614 | 7 | if (cur_remote == NULL) |
1615 | 7 | { |
1616 | 7 | if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) |
1617 | 0 | { |
1618 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
1619 | 0 | return FALSE; |
1620 | 0 | } |
1621 | 7 | } |
1622 | 0 | else if (cur_remote->file != NULL) |
1623 | 0 | { |
1624 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS, |
1625 | 0 | "Remote \"%s\" already defined in %s", new_remote->name, |
1626 | 0 | gs_file_get_path_cached (cur_remote->file)); |
1627 | 0 | return FALSE; |
1628 | 0 | } |
1629 | 7 | } |
1630 | 275 | } |
1631 | | |
1632 | 139 | gsize len; |
1633 | 139 | g_autofree char *data = g_key_file_to_data (new_config, &len, error); |
1634 | 139 | if (!glnx_file_replace_contents_at (self->repo_dir_fd, "config", (guint8 *)data, len, 0, NULL, |
1635 | 139 | error)) |
1636 | 0 | return FALSE; |
1637 | | |
1638 | 139 | g_key_file_free (self->config); |
1639 | 139 | self->config = g_key_file_new (); |
1640 | 139 | if (!g_key_file_load_from_data (self->config, data, len, 0, error)) |
1641 | 0 | return FALSE; |
1642 | | |
1643 | 139 | return TRUE; |
1644 | 139 | } |
1645 | | |
1646 | | /** |
1647 | | * ostree_repo_write_config_and_reload: |
1648 | | * @self: Repo |
1649 | | * @new_config: Overwrite the config file with this data, and reload |
1650 | | * @error: a #GError |
1651 | | * |
1652 | | * Save @new_config in place of this repository's config file and reload. |
1653 | | * The config will be validated. |
1654 | | */ |
1655 | | gboolean |
1656 | | ostree_repo_write_config_and_reload (OstreeRepo *self, GKeyFile *new_config, GError **error) |
1657 | 0 | { |
1658 | 0 | g_return_val_if_fail (self->inited, FALSE); |
1659 | | |
1660 | 0 | g_autoptr (GKeyFile) old_config = g_steal_pointer (&self->config); |
1661 | | // Test reloading with the new config |
1662 | 0 | self->config = new_config; |
1663 | 0 | gboolean r = reload_config_inner (self, NULL, error); |
1664 | 0 | self->config = g_steal_pointer (&old_config); |
1665 | 0 | if (!r) |
1666 | 0 | { |
1667 | | // Best effort to revert back to the old config, but if that fails |
1668 | | // we're in a doubly bad state. |
1669 | 0 | (void)reload_config_inner (self, NULL, NULL); |
1670 | 0 | return FALSE; |
1671 | 0 | } |
1672 | | // Now perform the actual write |
1673 | 0 | return ostree_repo_write_config (self, new_config, error); |
1674 | 0 | } |
1675 | | |
1676 | | /* Bind a subset of an a{sv} to options in a given GKeyfile section */ |
1677 | | static void |
1678 | | keyfile_set_from_vardict (GKeyFile *keyfile, const char *section, GVariant *vardict) |
1679 | 0 | { |
1680 | 0 | GVariantIter viter; |
1681 | 0 | const char *key; |
1682 | 0 | GVariant *val; |
1683 | |
|
1684 | 0 | g_variant_iter_init (&viter, vardict); |
1685 | 0 | while (g_variant_iter_loop (&viter, "{&s@v}", &key, &val)) |
1686 | 0 | { |
1687 | 0 | g_autoptr (GVariant) child = g_variant_get_variant (val); |
1688 | 0 | if (g_variant_is_of_type (child, G_VARIANT_TYPE_STRING)) |
1689 | 0 | g_key_file_set_string (keyfile, section, key, g_variant_get_string (child, NULL)); |
1690 | 0 | else if (g_variant_is_of_type (child, G_VARIANT_TYPE_BOOLEAN)) |
1691 | 0 | g_key_file_set_boolean (keyfile, section, key, g_variant_get_boolean (child)); |
1692 | 0 | else if (g_variant_is_of_type (child, G_VARIANT_TYPE_STRING_ARRAY)) |
1693 | 0 | { |
1694 | 0 | gsize len; |
1695 | 0 | g_autofree const gchar **strv_child = g_variant_get_strv (child, &len); |
1696 | 0 | g_key_file_set_string_list (keyfile, section, key, strv_child, len); |
1697 | 0 | } |
1698 | 0 | else |
1699 | 0 | g_critical ("Unhandled type '%s' in %s", (char *)g_variant_get_type (child), G_STRFUNC); |
1700 | 0 | } |
1701 | 0 | } |
1702 | | |
1703 | | static gboolean |
1704 | | impl_repo_remote_add (OstreeRepo *self, GFile *sysroot, gboolean if_not_exists, const char *name, |
1705 | | const char *url, GVariant *options, GCancellable *cancellable, GError **error) |
1706 | 0 | { |
1707 | 0 | g_return_val_if_fail (name != NULL, FALSE); |
1708 | 0 | g_return_val_if_fail (options == NULL || g_variant_is_of_type (options, G_VARIANT_TYPE ("a{sv}")), |
1709 | 0 | FALSE); |
1710 | | |
1711 | 0 | if (!ostree_validate_remote_name (name, error)) |
1712 | 0 | return FALSE; |
1713 | | |
1714 | 0 | g_autoptr (OstreeRemote) remote = _ostree_repo_get_remote (self, name, NULL); |
1715 | 0 | if (remote != NULL && if_not_exists) |
1716 | 0 | { |
1717 | | /* Note early return */ |
1718 | 0 | return TRUE; |
1719 | 0 | } |
1720 | 0 | else if (remote != NULL) |
1721 | 0 | { |
1722 | 0 | return glnx_throw (error, "Remote configuration for \"%s\" already exists: %s", name, |
1723 | 0 | remote->file ? gs_file_get_path_cached (remote->file) : "(in config)"); |
1724 | 0 | } |
1725 | | |
1726 | 0 | remote = ostree_remote_new (name); |
1727 | | |
1728 | | /* Only add repos in remotes.d if the repo option |
1729 | | * add-remotes-config-dir is true. This is the default for system |
1730 | | * repos. |
1731 | | */ |
1732 | 0 | g_autoptr (GFile) etc_ostree_remotes_d = get_remotes_d_dir (self, sysroot); |
1733 | 0 | if (etc_ostree_remotes_d && self->add_remotes_config_dir) |
1734 | 0 | { |
1735 | 0 | g_autoptr (GError) local_error = NULL; |
1736 | |
|
1737 | 0 | if (!g_file_make_directory_with_parents (etc_ostree_remotes_d, cancellable, &local_error)) |
1738 | 0 | { |
1739 | 0 | if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) |
1740 | 0 | { |
1741 | 0 | g_clear_error (&local_error); |
1742 | 0 | } |
1743 | 0 | else |
1744 | 0 | { |
1745 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
1746 | 0 | return FALSE; |
1747 | 0 | } |
1748 | 0 | } |
1749 | | |
1750 | 0 | g_autofree char *basename = g_strconcat (name, ".conf", NULL); |
1751 | 0 | remote->file = g_file_get_child (etc_ostree_remotes_d, basename); |
1752 | 0 | } |
1753 | | |
1754 | 0 | if (url) |
1755 | 0 | { |
1756 | 0 | if (g_str_has_prefix (url, "metalink=")) |
1757 | 0 | g_key_file_set_string (remote->options, remote->group, "metalink", |
1758 | 0 | url + strlen ("metalink=")); |
1759 | 0 | else |
1760 | 0 | g_key_file_set_string (remote->options, remote->group, "url", url); |
1761 | 0 | } |
1762 | |
|
1763 | 0 | if (options) |
1764 | 0 | keyfile_set_from_vardict (remote->options, remote->group, options); |
1765 | |
|
1766 | 0 | if (remote->file != NULL) |
1767 | 0 | { |
1768 | 0 | gsize length; |
1769 | 0 | g_autofree char *data = g_key_file_to_data (remote->options, &length, NULL); |
1770 | |
|
1771 | 0 | if (!g_file_replace_contents (remote->file, data, length, NULL, FALSE, 0, NULL, cancellable, |
1772 | 0 | error)) |
1773 | 0 | return FALSE; |
1774 | 0 | } |
1775 | 0 | else |
1776 | 0 | { |
1777 | 0 | g_autoptr (GKeyFile) config = NULL; |
1778 | |
|
1779 | 0 | config = ostree_repo_copy_config (self); |
1780 | 0 | ot_keyfile_copy_group (remote->options, config, remote->group); |
1781 | |
|
1782 | 0 | if (!ostree_repo_write_config (self, config, error)) |
1783 | 0 | return FALSE; |
1784 | 0 | } |
1785 | | |
1786 | 0 | _ostree_repo_add_remote (self, remote); |
1787 | |
|
1788 | 0 | return TRUE; |
1789 | 0 | } |
1790 | | |
1791 | | /** |
1792 | | * ostree_repo_remote_add: |
1793 | | * @self: Repo |
1794 | | * @name: Name of remote |
1795 | | * @url: (allow-none): URL for remote (if URL begins with metalink=, it will be used as such) |
1796 | | * @options: (allow-none): GVariant of type a{sv} |
1797 | | * @cancellable: Cancellable |
1798 | | * @error: Error |
1799 | | * |
1800 | | * Create a new remote named @name pointing to @url. If @options is |
1801 | | * provided, then it will be mapped to #GKeyFile entries, where the |
1802 | | * GVariant dictionary key is an option string, and the value is |
1803 | | * mapped as follows: |
1804 | | * * s: g_key_file_set_string() |
1805 | | * * b: g_key_file_set_boolean() |
1806 | | * * as: g_key_file_set_string_list() |
1807 | | * |
1808 | | */ |
1809 | | gboolean |
1810 | | ostree_repo_remote_add (OstreeRepo *self, const char *name, const char *url, GVariant *options, |
1811 | | GCancellable *cancellable, GError **error) |
1812 | 0 | { |
1813 | 0 | return impl_repo_remote_add (self, NULL, FALSE, name, url, options, cancellable, error); |
1814 | 0 | } |
1815 | | |
1816 | | static gboolean |
1817 | | impl_repo_remote_delete (OstreeRepo *self, GFile *sysroot, gboolean if_exists, const char *name, |
1818 | | GCancellable *cancellable, GError **error) |
1819 | 0 | { |
1820 | 0 | g_return_val_if_fail (name != NULL, FALSE); |
1821 | | |
1822 | 0 | if (!ostree_validate_remote_name (name, error)) |
1823 | 0 | return FALSE; |
1824 | | |
1825 | 0 | g_autoptr (OstreeRemote) remote = NULL; |
1826 | 0 | if (if_exists) |
1827 | 0 | { |
1828 | 0 | remote = _ostree_repo_get_remote (self, name, NULL); |
1829 | 0 | if (!remote) |
1830 | 0 | { |
1831 | | /* Note early return */ |
1832 | 0 | return TRUE; |
1833 | 0 | } |
1834 | 0 | } |
1835 | 0 | else |
1836 | 0 | remote = _ostree_repo_get_remote (self, name, error); |
1837 | | |
1838 | 0 | if (remote == NULL) |
1839 | 0 | return FALSE; |
1840 | | |
1841 | 0 | if (remote->file != NULL) |
1842 | 0 | { |
1843 | 0 | if (!glnx_unlinkat (AT_FDCWD, gs_file_get_path_cached (remote->file), 0, error)) |
1844 | 0 | return FALSE; |
1845 | 0 | } |
1846 | 0 | else |
1847 | 0 | { |
1848 | 0 | g_autoptr (GKeyFile) config = ostree_repo_copy_config (self); |
1849 | | |
1850 | | /* XXX Not sure it's worth failing if the group to remove |
1851 | | * isn't found. It's the end result we want, after all. */ |
1852 | 0 | if (g_key_file_remove_group (config, remote->group, NULL)) |
1853 | 0 | { |
1854 | 0 | if (!ostree_repo_write_config (self, config, error)) |
1855 | 0 | return FALSE; |
1856 | 0 | } |
1857 | 0 | } |
1858 | | |
1859 | | /* Delete the remote's keyring file, if it exists. */ |
1860 | 0 | if (!ot_ensure_unlinked_at (self->repo_dir_fd, remote->keyring, error)) |
1861 | 0 | return FALSE; |
1862 | | |
1863 | 0 | _ostree_repo_remove_remote (self, remote); |
1864 | |
|
1865 | 0 | return TRUE; |
1866 | 0 | } |
1867 | | |
1868 | | /** |
1869 | | * ostree_repo_remote_delete: |
1870 | | * @self: Repo |
1871 | | * @name: Name of remote |
1872 | | * @cancellable: Cancellable |
1873 | | * @error: Error |
1874 | | * |
1875 | | * Delete the remote named @name. It is an error if the provided |
1876 | | * remote does not exist. |
1877 | | * |
1878 | | */ |
1879 | | gboolean |
1880 | | ostree_repo_remote_delete (OstreeRepo *self, const char *name, GCancellable *cancellable, |
1881 | | GError **error) |
1882 | 0 | { |
1883 | 0 | return impl_repo_remote_delete (self, NULL, FALSE, name, cancellable, error); |
1884 | 0 | } |
1885 | | |
1886 | | static gboolean |
1887 | | impl_repo_remote_replace (OstreeRepo *self, GFile *sysroot, const char *name, const char *url, |
1888 | | GVariant *options, GCancellable *cancellable, GError **error) |
1889 | 0 | { |
1890 | 0 | g_return_val_if_fail (name != NULL, FALSE); |
1891 | 0 | g_return_val_if_fail (options == NULL || g_variant_is_of_type (options, G_VARIANT_TYPE ("a{sv}")), |
1892 | 0 | FALSE); |
1893 | | |
1894 | 0 | if (!ostree_validate_remote_name (name, error)) |
1895 | 0 | return FALSE; |
1896 | | |
1897 | 0 | g_autoptr (GError) local_error = NULL; |
1898 | 0 | g_autoptr (OstreeRemote) remote = _ostree_repo_get_remote (self, name, &local_error); |
1899 | 0 | if (remote == NULL) |
1900 | 0 | { |
1901 | 0 | if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) |
1902 | 0 | { |
1903 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
1904 | 0 | return FALSE; |
1905 | 0 | } |
1906 | 0 | g_clear_error (&local_error); |
1907 | 0 | if (!impl_repo_remote_add (self, sysroot, FALSE, name, url, options, cancellable, error)) |
1908 | 0 | return FALSE; |
1909 | 0 | } |
1910 | 0 | else |
1911 | 0 | { |
1912 | | /* Replace the entire option group */ |
1913 | 0 | if (!g_key_file_remove_group (remote->options, remote->group, error)) |
1914 | 0 | return FALSE; |
1915 | | |
1916 | 0 | if (url) |
1917 | 0 | { |
1918 | 0 | if (g_str_has_prefix (url, "metalink=")) |
1919 | 0 | g_key_file_set_string (remote->options, remote->group, "metalink", |
1920 | 0 | url + strlen ("metalink=")); |
1921 | 0 | else |
1922 | 0 | g_key_file_set_string (remote->options, remote->group, "url", url); |
1923 | 0 | } |
1924 | |
|
1925 | 0 | if (options != NULL) |
1926 | 0 | keyfile_set_from_vardict (remote->options, remote->group, options); |
1927 | | |
1928 | | /* Write out updated settings */ |
1929 | 0 | if (remote->file != NULL) |
1930 | 0 | { |
1931 | 0 | gsize length; |
1932 | 0 | g_autofree char *data = g_key_file_to_data (remote->options, &length, NULL); |
1933 | |
|
1934 | 0 | if (!g_file_replace_contents (remote->file, data, length, NULL, FALSE, 0, NULL, |
1935 | 0 | cancellable, error)) |
1936 | 0 | return FALSE; |
1937 | 0 | } |
1938 | 0 | else |
1939 | 0 | { |
1940 | 0 | g_autoptr (GKeyFile) config = ostree_repo_copy_config (self); |
1941 | | |
1942 | | /* Remove the existing group if it exists */ |
1943 | 0 | if (!g_key_file_remove_group (config, remote->group, &local_error)) |
1944 | 0 | { |
1945 | 0 | if (!g_error_matches (local_error, G_KEY_FILE_ERROR, |
1946 | 0 | G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) |
1947 | 0 | { |
1948 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
1949 | 0 | return FALSE; |
1950 | 0 | } |
1951 | 0 | } |
1952 | | |
1953 | 0 | ot_keyfile_copy_group (remote->options, config, remote->group); |
1954 | 0 | if (!ostree_repo_write_config (self, config, error)) |
1955 | 0 | return FALSE; |
1956 | 0 | } |
1957 | 0 | } |
1958 | | |
1959 | 0 | return TRUE; |
1960 | 0 | } |
1961 | | |
1962 | | /** |
1963 | | * ostree_repo_remote_change: |
1964 | | * @self: Repo |
1965 | | * @sysroot: (allow-none): System root |
1966 | | * @changeop: Operation to perform |
1967 | | * @name: Name of remote |
1968 | | * @url: (allow-none): URL for remote (if URL begins with metalink=, it will be used as such) |
1969 | | * @options: (allow-none): GVariant of type a{sv} |
1970 | | * @cancellable: Cancellable |
1971 | | * @error: Error |
1972 | | * |
1973 | | * A combined function handling the equivalent of |
1974 | | * ostree_repo_remote_add(), ostree_repo_remote_delete(), with more |
1975 | | * options. |
1976 | | * |
1977 | | * |
1978 | | */ |
1979 | | gboolean |
1980 | | ostree_repo_remote_change (OstreeRepo *self, GFile *sysroot, OstreeRepoRemoteChange changeop, |
1981 | | const char *name, const char *url, GVariant *options, |
1982 | | GCancellable *cancellable, GError **error) |
1983 | 0 | { |
1984 | 0 | switch (changeop) |
1985 | 0 | { |
1986 | 0 | case OSTREE_REPO_REMOTE_CHANGE_ADD: |
1987 | 0 | return impl_repo_remote_add (self, sysroot, FALSE, name, url, options, cancellable, error); |
1988 | 0 | case OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS: |
1989 | 0 | return impl_repo_remote_add (self, sysroot, TRUE, name, url, options, cancellable, error); |
1990 | 0 | case OSTREE_REPO_REMOTE_CHANGE_DELETE: |
1991 | 0 | return impl_repo_remote_delete (self, sysroot, FALSE, name, cancellable, error); |
1992 | 0 | case OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS: |
1993 | 0 | return impl_repo_remote_delete (self, sysroot, TRUE, name, cancellable, error); |
1994 | 0 | case OSTREE_REPO_REMOTE_CHANGE_REPLACE: |
1995 | 0 | return impl_repo_remote_replace (self, sysroot, name, url, options, cancellable, error); |
1996 | 0 | } |
1997 | 0 | g_assert_not_reached (); |
1998 | 0 | } |
1999 | | |
2000 | | static void |
2001 | | _ostree_repo_remote_list (OstreeRepo *self, GHashTable *out) |
2002 | 0 | { |
2003 | 0 | GHashTableIter iter; |
2004 | 0 | gpointer key, value; |
2005 | |
|
2006 | 0 | g_mutex_lock (&self->remotes_lock); |
2007 | |
|
2008 | 0 | g_hash_table_iter_init (&iter, self->remotes); |
2009 | 0 | while (g_hash_table_iter_next (&iter, &key, &value)) |
2010 | 0 | g_hash_table_insert (out, g_strdup (key), NULL); |
2011 | |
|
2012 | 0 | g_mutex_unlock (&self->remotes_lock); |
2013 | |
|
2014 | 0 | if (self->parent_repo) |
2015 | 0 | _ostree_repo_remote_list (self->parent_repo, out); |
2016 | 0 | } |
2017 | | |
2018 | | /** |
2019 | | * ostree_repo_remote_list: |
2020 | | * @self: Repo |
2021 | | * @out_n_remotes: (out) (allow-none): Number of remotes available |
2022 | | * |
2023 | | * List available remote names in an #OstreeRepo. Remote names are sorted |
2024 | | * alphabetically. If no remotes are available the function returns %NULL. |
2025 | | * |
2026 | | * Returns: (array length=out_n_remotes) (transfer full): a %NULL-terminated |
2027 | | * array of remote names |
2028 | | **/ |
2029 | | char ** |
2030 | | ostree_repo_remote_list (OstreeRepo *self, guint *out_n_remotes) |
2031 | 0 | { |
2032 | 0 | char **remotes = NULL; |
2033 | 0 | guint n_remotes; |
2034 | 0 | g_autoptr (GHashTable) remotes_ht = NULL; |
2035 | |
|
2036 | 0 | remotes_ht = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, |
2037 | 0 | (GDestroyNotify)NULL); |
2038 | |
|
2039 | 0 | _ostree_repo_remote_list (self, remotes_ht); |
2040 | |
|
2041 | 0 | n_remotes = g_hash_table_size (remotes_ht); |
2042 | |
|
2043 | 0 | if (n_remotes > 0) |
2044 | 0 | { |
2045 | 0 | GList *list, *link; |
2046 | 0 | guint ii = 0; |
2047 | |
|
2048 | 0 | remotes = g_new (char *, n_remotes + 1); |
2049 | |
|
2050 | 0 | list = g_hash_table_get_keys (remotes_ht); |
2051 | 0 | list = g_list_sort (list, (GCompareFunc)strcmp); |
2052 | |
|
2053 | 0 | for (link = list; link != NULL; link = link->next) |
2054 | 0 | remotes[ii++] = g_strdup (link->data); |
2055 | |
|
2056 | 0 | g_list_free (list); |
2057 | |
|
2058 | 0 | remotes[ii] = NULL; |
2059 | 0 | } |
2060 | |
|
2061 | 0 | if (out_n_remotes) |
2062 | 0 | *out_n_remotes = n_remotes; |
2063 | |
|
2064 | 0 | return remotes; |
2065 | 0 | } |
2066 | | |
2067 | | /** |
2068 | | * ostree_repo_remote_get_url: |
2069 | | * @self: Repo |
2070 | | * @name: Name of remote |
2071 | | * @out_url: (out) (optional): Remote's URL |
2072 | | * @error: Error |
2073 | | * |
2074 | | * Return the URL of the remote named @name through @out_url. It is an |
2075 | | * error if the provided remote does not exist. |
2076 | | * |
2077 | | * Returns: %TRUE on success, %FALSE on failure |
2078 | | */ |
2079 | | gboolean |
2080 | | ostree_repo_remote_get_url (OstreeRepo *self, const char *name, char **out_url, GError **error) |
2081 | 0 | { |
2082 | 0 | g_return_val_if_fail (name != NULL, FALSE); |
2083 | | |
2084 | 0 | g_autofree char *url = NULL; |
2085 | 0 | if (_ostree_repo_remote_name_is_file (name)) |
2086 | 0 | { |
2087 | 0 | url = g_strdup (name); |
2088 | 0 | } |
2089 | 0 | else |
2090 | 0 | { |
2091 | 0 | if (!ostree_repo_get_remote_option (self, name, "url", NULL, &url, error)) |
2092 | 0 | return FALSE; |
2093 | | |
2094 | 0 | if (url == NULL) |
2095 | 0 | { |
2096 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, |
2097 | 0 | "No \"url\" option in remote \"%s\"", name); |
2098 | 0 | return FALSE; |
2099 | 0 | } |
2100 | 0 | } |
2101 | | |
2102 | 0 | if (out_url != NULL) |
2103 | 0 | *out_url = g_steal_pointer (&url); |
2104 | 0 | return TRUE; |
2105 | 0 | } |
2106 | | |
2107 | | /** |
2108 | | * ostree_repo_remote_get_gpg_verify: |
2109 | | * @self: Repo |
2110 | | * @name: Name of remote |
2111 | | * @out_gpg_verify: (out) (optional): Remote's GPG option |
2112 | | * @error: Error |
2113 | | * |
2114 | | * Return whether GPG verification is enabled for the remote named @name |
2115 | | * through @out_gpg_verify. It is an error if the provided remote does |
2116 | | * not exist. |
2117 | | * |
2118 | | * Returns: %TRUE on success, %FALSE on failure |
2119 | | */ |
2120 | | gboolean |
2121 | | ostree_repo_remote_get_gpg_verify (OstreeRepo *self, const char *name, gboolean *out_gpg_verify, |
2122 | | GError **error) |
2123 | 0 | { |
2124 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); |
2125 | 0 | g_return_val_if_fail (name != NULL, FALSE); |
2126 | | |
2127 | | /* For compatibility with pull-local, don't GPG verify file:// URIs. */ |
2128 | 0 | if (_ostree_repo_remote_name_is_file (name)) |
2129 | 0 | { |
2130 | 0 | if (out_gpg_verify != NULL) |
2131 | 0 | *out_gpg_verify = FALSE; |
2132 | 0 | return TRUE; |
2133 | 0 | } |
2134 | | |
2135 | 0 | return ostree_repo_get_remote_boolean_option (self, name, "gpg-verify", TRUE, out_gpg_verify, |
2136 | 0 | error); |
2137 | 0 | } |
2138 | | |
2139 | | /** |
2140 | | * ostree_repo_remote_get_gpg_verify_summary: |
2141 | | * @self: Repo |
2142 | | * @name: Name of remote |
2143 | | * @out_gpg_verify_summary: (out) (allow-none): Remote's GPG option |
2144 | | * @error: Error |
2145 | | * |
2146 | | * Return whether GPG verification of the summary is enabled for the remote |
2147 | | * named @name through @out_gpg_verify_summary. It is an error if the provided |
2148 | | * remote does not exist. |
2149 | | * |
2150 | | * Returns: %TRUE on success, %FALSE on failure |
2151 | | */ |
2152 | | gboolean |
2153 | | ostree_repo_remote_get_gpg_verify_summary (OstreeRepo *self, const char *name, |
2154 | | gboolean *out_gpg_verify_summary, GError **error) |
2155 | 0 | { |
2156 | 0 | return ostree_repo_get_remote_boolean_option (self, name, "gpg-verify-summary", FALSE, |
2157 | 0 | out_gpg_verify_summary, error); |
2158 | 0 | } |
2159 | | |
2160 | | /** |
2161 | | * ostree_repo_remote_gpg_import: |
2162 | | * @self: Self |
2163 | | * @name: name of a remote |
2164 | | * @source_stream: (nullable): a #GInputStream, or %NULL |
2165 | | * @key_ids: (array zero-terminated=1) (element-type utf8) (nullable): a %NULL-terminated array of |
2166 | | * GPG key IDs, or %NULL |
2167 | | * @out_imported: (out) (optional): return location for the number of imported |
2168 | | * keys, or %NULL |
2169 | | * @cancellable: a #GCancellable |
2170 | | * @error: a #GError |
2171 | | * |
2172 | | * Imports one or more GPG keys from the open @source_stream, or from the |
2173 | | * user's personal keyring if @source_stream is %NULL. The @key_ids array |
2174 | | * can optionally restrict which keys are imported. If @key_ids is %NULL, |
2175 | | * then all keys are imported. |
2176 | | * |
2177 | | * The imported keys will be used to conduct GPG verification when pulling |
2178 | | * from the remote named @name. |
2179 | | * |
2180 | | * Returns: %TRUE on success, %FALSE on failure |
2181 | | */ |
2182 | | gboolean |
2183 | | ostree_repo_remote_gpg_import (OstreeRepo *self, const char *name, GInputStream *source_stream, |
2184 | | const char *const *key_ids, guint *out_imported, |
2185 | | GCancellable *cancellable, GError **error) |
2186 | 0 | { |
2187 | 0 | #ifndef OSTREE_DISABLE_GPGME |
2188 | 0 | OstreeRemote *remote; |
2189 | 0 | g_auto (gpgme_ctx_t) source_context = NULL; |
2190 | 0 | g_auto (gpgme_ctx_t) target_context = NULL; |
2191 | 0 | g_auto (gpgme_data_t) data_buffer = NULL; |
2192 | 0 | gpgme_import_result_t import_result; |
2193 | 0 | gpgme_import_status_t import_status; |
2194 | 0 | g_autofree char *source_tmp_dir = NULL; |
2195 | 0 | g_autofree char *target_tmp_dir = NULL; |
2196 | 0 | glnx_autofd int target_temp_fd = -1; |
2197 | 0 | g_autoptr (GPtrArray) keys = NULL; |
2198 | 0 | struct stat stbuf; |
2199 | 0 | gpgme_error_t gpg_error; |
2200 | 0 | gboolean ret = FALSE; |
2201 | |
|
2202 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); |
2203 | 0 | g_return_val_if_fail (name != NULL, FALSE); |
2204 | | |
2205 | | /* First make sure the remote name is valid. */ |
2206 | | |
2207 | 0 | remote = _ostree_repo_get_remote_inherited (self, name, error); |
2208 | 0 | if (remote == NULL) |
2209 | 0 | goto out; |
2210 | | |
2211 | | /* Prepare the source GPGME context. If reading GPG keys from an input |
2212 | | * stream, point the OpenPGP engine at a temporary directory and import |
2213 | | * the keys to a new pubring.gpg file. If the key data format is ASCII |
2214 | | * armored, this step will convert them to binary. */ |
2215 | | |
2216 | 0 | source_context = ot_gpgme_new_ctx (NULL, error); |
2217 | 0 | if (!source_context) |
2218 | 0 | goto out; |
2219 | | |
2220 | 0 | if (source_stream != NULL) |
2221 | 0 | { |
2222 | 0 | data_buffer = ot_gpgme_data_input (source_stream); |
2223 | |
|
2224 | 0 | if (!ot_gpgme_ctx_tmp_home_dir (source_context, &source_tmp_dir, NULL, cancellable, error)) |
2225 | 0 | { |
2226 | 0 | g_prefix_error (error, "Unable to configure context: "); |
2227 | 0 | goto out; |
2228 | 0 | } |
2229 | | |
2230 | 0 | gpg_error = gpgme_op_import (source_context, data_buffer); |
2231 | 0 | if (gpg_error != GPG_ERR_NO_ERROR) |
2232 | 0 | { |
2233 | 0 | ot_gpgme_throw (gpg_error, error, "Unable to import keys"); |
2234 | 0 | goto out; |
2235 | 0 | } |
2236 | | |
2237 | 0 | g_clear_pointer (&data_buffer, gpgme_data_release); |
2238 | 0 | } |
2239 | | |
2240 | | /* Retrieve all keys or specific keys from the source GPGME context. |
2241 | | * Assemble a NULL-terminated array of gpgme_key_t structs to import. */ |
2242 | | |
2243 | | /* The keys array will contain a NULL terminator, but it turns out, |
2244 | | * although not documented, gpgme_key_unref() gracefully handles it. */ |
2245 | 0 | keys = g_ptr_array_new_with_free_func ((GDestroyNotify)gpgme_key_unref); |
2246 | |
|
2247 | 0 | if (key_ids != NULL) |
2248 | 0 | { |
2249 | 0 | guint ii; |
2250 | |
|
2251 | 0 | for (ii = 0; key_ids[ii] != NULL; ii++) |
2252 | 0 | { |
2253 | 0 | gpgme_key_t key = NULL; |
2254 | |
|
2255 | 0 | gpg_error = gpgme_get_key (source_context, key_ids[ii], &key, 0); |
2256 | 0 | if (gpg_error != GPG_ERR_NO_ERROR) |
2257 | 0 | { |
2258 | 0 | ot_gpgme_throw (gpg_error, error, "Unable to find key \"%s\"", key_ids[ii]); |
2259 | 0 | goto out; |
2260 | 0 | } |
2261 | | |
2262 | | /* Transfer ownership. */ |
2263 | 0 | g_ptr_array_add (keys, key); |
2264 | 0 | } |
2265 | 0 | } |
2266 | 0 | else |
2267 | 0 | { |
2268 | 0 | gpg_error = gpgme_op_keylist_start (source_context, NULL, 0); |
2269 | |
|
2270 | 0 | while (gpg_error == GPG_ERR_NO_ERROR) |
2271 | 0 | { |
2272 | 0 | gpgme_key_t key = NULL; |
2273 | |
|
2274 | 0 | gpg_error = gpgme_op_keylist_next (source_context, &key); |
2275 | |
|
2276 | 0 | if (gpg_error != GPG_ERR_NO_ERROR) |
2277 | 0 | break; |
2278 | | |
2279 | | /* Transfer ownership. */ |
2280 | 0 | g_ptr_array_add (keys, key); |
2281 | 0 | } |
2282 | |
|
2283 | 0 | if (gpgme_err_code (gpg_error) != GPG_ERR_EOF) |
2284 | 0 | { |
2285 | 0 | ot_gpgme_throw (gpg_error, error, "Unable to list keys"); |
2286 | 0 | goto out; |
2287 | 0 | } |
2288 | 0 | } |
2289 | | |
2290 | | /* Add the NULL terminator. */ |
2291 | 0 | g_ptr_array_add (keys, NULL); |
2292 | | |
2293 | | /* Prepare the target GPGME context to serve as the import destination. |
2294 | | * Here the pubring.gpg file in a second temporary directory is a copy |
2295 | | * of the remote's keyring file. We'll let the import operation alter |
2296 | | * the pubring.gpg file, then rename it back to its permanent home. */ |
2297 | |
|
2298 | 0 | target_context = ot_gpgme_new_ctx (NULL, error); |
2299 | 0 | if (!target_context) |
2300 | 0 | goto out; |
2301 | | |
2302 | | /* No need for an output stream since we copy in a pubring.gpg. */ |
2303 | 0 | if (!ot_gpgme_ctx_tmp_home_dir (target_context, &target_tmp_dir, NULL, cancellable, error)) |
2304 | 0 | { |
2305 | 0 | g_prefix_error (error, "Unable to configure context: "); |
2306 | 0 | goto out; |
2307 | 0 | } |
2308 | | |
2309 | 0 | if (!glnx_opendirat (AT_FDCWD, target_tmp_dir, FALSE, &target_temp_fd, error)) |
2310 | 0 | { |
2311 | 0 | g_prefix_error (error, "Unable to open directory: "); |
2312 | 0 | goto out; |
2313 | 0 | } |
2314 | | |
2315 | 0 | if (fstatat (self->repo_dir_fd, remote->keyring, &stbuf, AT_SYMLINK_NOFOLLOW) == 0) |
2316 | 0 | { |
2317 | 0 | if (!glnx_file_copy_at (self->repo_dir_fd, remote->keyring, &stbuf, target_temp_fd, |
2318 | 0 | "pubring.gpg", GLNX_FILE_COPY_NOXATTRS, cancellable, error)) |
2319 | 0 | { |
2320 | 0 | g_prefix_error (error, "Unable to copy remote's keyring: "); |
2321 | 0 | goto out; |
2322 | 0 | } |
2323 | 0 | } |
2324 | 0 | else if (errno == ENOENT) |
2325 | 0 | { |
2326 | 0 | glnx_autofd int fd = -1; |
2327 | | |
2328 | | /* Create an empty pubring.gpg file prior to importing keys. This |
2329 | | * prevents gpg2 from creating a pubring.kbx file in the new keybox |
2330 | | * format [1]. We want to stay with the older keyring format since |
2331 | | * its performance issues are not relevant here. |
2332 | | * |
2333 | | * [1] https://gnupg.org/faq/whats-new-in-2.1.html#keybox |
2334 | | */ |
2335 | 0 | fd = openat (target_temp_fd, "pubring.gpg", O_WRONLY | O_CREAT | O_CLOEXEC | O_NOCTTY, 0644); |
2336 | 0 | if (fd == -1) |
2337 | 0 | { |
2338 | 0 | glnx_set_prefix_error_from_errno (error, "%s", "Unable to create pubring.gpg"); |
2339 | 0 | goto out; |
2340 | 0 | } |
2341 | 0 | } |
2342 | 0 | else |
2343 | 0 | { |
2344 | 0 | glnx_set_prefix_error_from_errno (error, "%s", "Unable to copy remote's keyring"); |
2345 | 0 | goto out; |
2346 | 0 | } |
2347 | | |
2348 | | /* Export the selected keys from the source context and import them into |
2349 | | * the target context. */ |
2350 | | |
2351 | 0 | gpg_error = gpgme_data_new (&data_buffer); |
2352 | 0 | if (gpg_error != GPG_ERR_NO_ERROR) |
2353 | 0 | { |
2354 | 0 | ot_gpgme_throw (gpg_error, error, "Unable to create data buffer"); |
2355 | 0 | goto out; |
2356 | 0 | } |
2357 | | |
2358 | 0 | gpg_error = gpgme_op_export_keys (source_context, (gpgme_key_t *)keys->pdata, 0, data_buffer); |
2359 | 0 | if (gpg_error != GPG_ERR_NO_ERROR) |
2360 | 0 | { |
2361 | 0 | ot_gpgme_throw (gpg_error, error, "Unable to export keys"); |
2362 | 0 | goto out; |
2363 | 0 | } |
2364 | | |
2365 | 0 | (void)gpgme_data_seek (data_buffer, 0, SEEK_SET); |
2366 | |
|
2367 | 0 | gpg_error = gpgme_op_import (target_context, data_buffer); |
2368 | 0 | if (gpg_error != GPG_ERR_NO_ERROR) |
2369 | 0 | { |
2370 | 0 | ot_gpgme_throw (gpg_error, error, "Unable to import keys"); |
2371 | 0 | goto out; |
2372 | 0 | } |
2373 | | |
2374 | 0 | import_result = gpgme_op_import_result (target_context); |
2375 | 0 | g_return_val_if_fail (import_result != NULL, FALSE); |
2376 | | |
2377 | | /* Check the status of each import and fail on the first error. |
2378 | | * All imports must be successful to update the remote's keyring. */ |
2379 | 0 | for (import_status = import_result->imports; import_status != NULL; |
2380 | 0 | import_status = import_status->next) |
2381 | 0 | { |
2382 | 0 | if (import_status->result != GPG_ERR_NO_ERROR) |
2383 | 0 | { |
2384 | 0 | ot_gpgme_throw (gpg_error, error, "Unable to import key \"%s\"", import_status->fpr); |
2385 | 0 | goto out; |
2386 | 0 | } |
2387 | 0 | } |
2388 | | |
2389 | | /* Import successful; replace the remote's old keyring with the |
2390 | | * updated keyring in the target context's temporary directory. */ |
2391 | 0 | if (!glnx_file_copy_at (target_temp_fd, "pubring.gpg", NULL, self->repo_dir_fd, remote->keyring, |
2392 | 0 | GLNX_FILE_COPY_NOXATTRS | GLNX_FILE_COPY_OVERWRITE, cancellable, error)) |
2393 | 0 | goto out; |
2394 | | |
2395 | 0 | if (out_imported != NULL) |
2396 | 0 | *out_imported = (guint)import_result->imported; |
2397 | |
|
2398 | 0 | ret = TRUE; |
2399 | |
|
2400 | 0 | out: |
2401 | 0 | if (remote != NULL) |
2402 | 0 | ostree_remote_unref (remote); |
2403 | |
|
2404 | 0 | if (source_tmp_dir != NULL) |
2405 | 0 | { |
2406 | 0 | ot_gpgme_kill_agent (source_tmp_dir); |
2407 | 0 | (void)glnx_shutil_rm_rf_at (AT_FDCWD, source_tmp_dir, NULL, NULL); |
2408 | 0 | } |
2409 | |
|
2410 | 0 | if (target_tmp_dir != NULL) |
2411 | 0 | { |
2412 | 0 | ot_gpgme_kill_agent (target_tmp_dir); |
2413 | 0 | (void)glnx_shutil_rm_rf_at (AT_FDCWD, target_tmp_dir, NULL, NULL); |
2414 | 0 | } |
2415 | |
|
2416 | 0 | g_prefix_error (error, "GPG: "); |
2417 | |
|
2418 | 0 | return ret; |
2419 | | #else /* OSTREE_DISABLE_GPGME */ |
2420 | | return glnx_throw (error, "GPG feature is disabled in a build time"); |
2421 | | #endif /* OSTREE_DISABLE_GPGME */ |
2422 | 0 | } |
2423 | | |
2424 | | #ifndef OSTREE_DISABLE_GPGME |
2425 | | static gboolean _ostree_repo_gpg_prepare_verifier (OstreeRepo *self, const gchar *remote_name, |
2426 | | GFile *keyringdir, GFile *extra_keyring, |
2427 | | gboolean add_global_keyrings, |
2428 | | OstreeGpgVerifier **out_verifier, |
2429 | | GCancellable *cancellable, GError **error); |
2430 | | #endif /* OSTREE_DISABLE_GPGME */ |
2431 | | |
2432 | | /** |
2433 | | * ostree_repo_remote_get_gpg_keys: |
2434 | | * @self: an #OstreeRepo |
2435 | | * @name: (nullable): name of the remote or %NULL |
2436 | | * @key_ids: (array zero-terminated=1) (element-type utf8) (nullable): |
2437 | | * a %NULL-terminated array of GPG key IDs to include, or %NULL |
2438 | | * @out_keys: (out) (optional) (element-type GVariant) (transfer container): |
2439 | | * return location for a #GPtrArray of the remote's trusted GPG keys, or |
2440 | | * %NULL |
2441 | | * @cancellable: (nullable): a #GCancellable, or %NULL |
2442 | | * @error: return location for a #GError, or %NULL |
2443 | | * |
2444 | | * Enumerate the trusted GPG keys for the remote @name. If @name is |
2445 | | * %NULL, the global GPG keys will be returned. The keys will be |
2446 | | * returned in the @out_keys #GPtrArray. Each element in the array is a |
2447 | | * #GVariant of format %OSTREE_GPG_KEY_GVARIANT_FORMAT. The @key_ids |
2448 | | * array can be used to limit which keys are included. If @key_ids is |
2449 | | * %NULL, then all keys are included. |
2450 | | * |
2451 | | * Returns: %TRUE if the GPG keys could be enumerated, %FALSE otherwise |
2452 | | * |
2453 | | * Since: 2021.4 |
2454 | | */ |
2455 | | gboolean |
2456 | | ostree_repo_remote_get_gpg_keys (OstreeRepo *self, const char *name, const char *const *key_ids, |
2457 | | GPtrArray **out_keys, GCancellable *cancellable, GError **error) |
2458 | 0 | { |
2459 | 0 | #ifndef OSTREE_DISABLE_GPGME |
2460 | 0 | g_autoptr (OstreeGpgVerifier) verifier = NULL; |
2461 | 0 | gboolean global_keyrings = (name == NULL); |
2462 | 0 | if (!_ostree_repo_gpg_prepare_verifier (self, name, NULL, NULL, global_keyrings, &verifier, |
2463 | 0 | cancellable, error)) |
2464 | 0 | return FALSE; |
2465 | | |
2466 | 0 | g_autoptr (GPtrArray) gpg_keys = NULL; |
2467 | 0 | if (!_ostree_gpg_verifier_list_keys (verifier, key_ids, &gpg_keys, cancellable, error)) |
2468 | 0 | return FALSE; |
2469 | | |
2470 | 0 | g_autoptr (GPtrArray) keys = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref); |
2471 | 0 | for (guint i = 0; i < gpg_keys->len; i++) |
2472 | 0 | { |
2473 | 0 | gpgme_key_t key = gpg_keys->pdata[i]; |
2474 | |
|
2475 | 0 | g_auto (GVariantBuilder) subkeys_builder = OT_VARIANT_BUILDER_INITIALIZER; |
2476 | 0 | g_variant_builder_init (&subkeys_builder, G_VARIANT_TYPE ("aa{sv}")); |
2477 | 0 | g_auto (GVariantBuilder) uids_builder = OT_VARIANT_BUILDER_INITIALIZER; |
2478 | 0 | g_variant_builder_init (&uids_builder, G_VARIANT_TYPE ("aa{sv}")); |
2479 | 0 | for (gpgme_subkey_t subkey = key->subkeys; subkey != NULL; subkey = subkey->next) |
2480 | 0 | { |
2481 | 0 | g_auto (GVariantDict) subkey_dict = OT_VARIANT_BUILDER_INITIALIZER; |
2482 | 0 | g_variant_dict_init (&subkey_dict, NULL); |
2483 | 0 | g_variant_dict_insert_value (&subkey_dict, "fingerprint", |
2484 | 0 | g_variant_new_string (subkey->fpr)); |
2485 | 0 | g_variant_dict_insert_value (&subkey_dict, "created", |
2486 | 0 | g_variant_new_int64 (GINT64_TO_BE (subkey->timestamp))); |
2487 | 0 | g_variant_dict_insert_value (&subkey_dict, "expires", |
2488 | 0 | g_variant_new_int64 (GINT64_TO_BE (subkey->expires))); |
2489 | 0 | g_variant_dict_insert_value (&subkey_dict, "revoked", |
2490 | 0 | g_variant_new_boolean (subkey->revoked)); |
2491 | 0 | g_variant_dict_insert_value (&subkey_dict, "expired", |
2492 | 0 | g_variant_new_boolean (subkey->expired)); |
2493 | 0 | g_variant_dict_insert_value (&subkey_dict, "invalid", |
2494 | 0 | g_variant_new_boolean (subkey->invalid)); |
2495 | 0 | g_variant_builder_add (&subkeys_builder, "@a{sv}", g_variant_dict_end (&subkey_dict)); |
2496 | 0 | } |
2497 | |
|
2498 | 0 | for (gpgme_user_id_t uid = key->uids; uid != NULL; uid = uid->next) |
2499 | 0 | { |
2500 | | /* Get WKD update URLs if address set */ |
2501 | 0 | g_autofree char *advanced_url = NULL; |
2502 | 0 | g_autofree char *direct_url = NULL; |
2503 | 0 | if (uid->address != NULL) |
2504 | 0 | { |
2505 | 0 | if (!ot_gpg_wkd_urls (uid->address, &advanced_url, &direct_url, error)) |
2506 | 0 | return FALSE; |
2507 | 0 | } |
2508 | | |
2509 | 0 | g_auto (GVariantDict) uid_dict = OT_VARIANT_BUILDER_INITIALIZER; |
2510 | 0 | g_variant_dict_init (&uid_dict, NULL); |
2511 | 0 | g_variant_dict_insert_value (&uid_dict, "uid", g_variant_new_string (uid->uid)); |
2512 | 0 | g_variant_dict_insert_value (&uid_dict, "name", g_variant_new_string (uid->name)); |
2513 | 0 | g_variant_dict_insert_value (&uid_dict, "comment", g_variant_new_string (uid->comment)); |
2514 | 0 | g_variant_dict_insert_value (&uid_dict, "email", g_variant_new_string (uid->email)); |
2515 | 0 | g_variant_dict_insert_value (&uid_dict, "revoked", g_variant_new_boolean (uid->revoked)); |
2516 | 0 | g_variant_dict_insert_value (&uid_dict, "invalid", g_variant_new_boolean (uid->invalid)); |
2517 | 0 | g_variant_dict_insert_value (&uid_dict, "advanced_url", |
2518 | 0 | g_variant_new ("ms", advanced_url)); |
2519 | 0 | g_variant_dict_insert_value (&uid_dict, "direct_url", g_variant_new ("ms", direct_url)); |
2520 | 0 | g_variant_builder_add (&uids_builder, "@a{sv}", g_variant_dict_end (&uid_dict)); |
2521 | 0 | } |
2522 | | |
2523 | | /* Currently empty */ |
2524 | 0 | g_auto (GVariantDict) metadata_dict = OT_VARIANT_BUILDER_INITIALIZER; |
2525 | 0 | g_variant_dict_init (&metadata_dict, NULL); |
2526 | |
|
2527 | 0 | GVariant *key_variant = g_variant_new ( |
2528 | 0 | "(@aa{sv}@aa{sv}@a{sv})", g_variant_builder_end (&subkeys_builder), |
2529 | 0 | g_variant_builder_end (&uids_builder), g_variant_dict_end (&metadata_dict)); |
2530 | 0 | g_ptr_array_add (keys, g_variant_ref_sink (key_variant)); |
2531 | 0 | } |
2532 | | |
2533 | 0 | if (out_keys) |
2534 | 0 | *out_keys = g_steal_pointer (&keys); |
2535 | |
|
2536 | 0 | return TRUE; |
2537 | | #else /* OSTREE_DISABLE_GPGME */ |
2538 | | return glnx_throw (error, "GPG feature is disabled in a build time"); |
2539 | | #endif /* OSTREE_DISABLE_GPGME */ |
2540 | 0 | } |
2541 | | |
2542 | | /** |
2543 | | * ostree_repo_remote_fetch_summary: |
2544 | | * @self: Self |
2545 | | * @name: name of a remote |
2546 | | * @out_summary: (out) (optional): return location for raw summary data, or |
2547 | | * %NULL |
2548 | | * @out_signatures: (out) (optional): return location for raw summary |
2549 | | * signature data, or %NULL |
2550 | | * @cancellable: a #GCancellable |
2551 | | * @error: a #GError |
2552 | | * |
2553 | | * Tries to fetch the summary file and any GPG signatures on the summary file |
2554 | | * over HTTP, and returns the binary data in @out_summary and @out_signatures |
2555 | | * respectively. |
2556 | | * |
2557 | | * If no summary file exists on the remote server, @out_summary is set to |
2558 | | * @NULL. Likewise if the summary file is not signed, @out_signatures is |
2559 | | * set to @NULL. In either case the function still returns %TRUE. |
2560 | | * |
2561 | | * This method does not verify the signature of the downloaded summary file. |
2562 | | * Use ostree_repo_verify_summary() for that. |
2563 | | * |
2564 | | * Parse the summary data into a #GVariant using g_variant_new_from_bytes() |
2565 | | * with #OSTREE_SUMMARY_GVARIANT_FORMAT as the format string. |
2566 | | * |
2567 | | * Returns: %TRUE on success, %FALSE on failure |
2568 | | */ |
2569 | | gboolean |
2570 | | ostree_repo_remote_fetch_summary (OstreeRepo *self, const char *name, GBytes **out_summary, |
2571 | | GBytes **out_signatures, GCancellable *cancellable, |
2572 | | GError **error) |
2573 | 0 | { |
2574 | 0 | return ostree_repo_remote_fetch_summary_with_options (self, name, NULL, out_summary, |
2575 | 0 | out_signatures, cancellable, error); |
2576 | 0 | } |
2577 | | |
2578 | | static gboolean |
2579 | | ostree_repo_mode_to_string (OstreeRepoMode mode, const char **out_mode, GError **error) |
2580 | 139 | { |
2581 | 139 | const char *ret_mode; |
2582 | | |
2583 | 139 | switch (mode) |
2584 | 139 | { |
2585 | 0 | case OSTREE_REPO_MODE_BARE: |
2586 | 0 | ret_mode = "bare"; |
2587 | 0 | break; |
2588 | 0 | case OSTREE_REPO_MODE_BARE_USER: |
2589 | 0 | ret_mode = "bare-user"; |
2590 | 0 | break; |
2591 | 0 | case OSTREE_REPO_MODE_BARE_USER_ONLY: |
2592 | 0 | ret_mode = "bare-user-only"; |
2593 | 0 | break; |
2594 | 139 | case OSTREE_REPO_MODE_ARCHIVE: |
2595 | | /* Legacy alias */ |
2596 | 139 | ret_mode = "archive-z2"; |
2597 | 139 | break; |
2598 | 0 | case OSTREE_REPO_MODE_BARE_SPLIT_XATTRS: |
2599 | 0 | ret_mode = "bare-split-xattrs"; |
2600 | 0 | break; |
2601 | 0 | default: |
2602 | 0 | return glnx_throw (error, "Invalid mode '%d'", mode); |
2603 | 139 | } |
2604 | | |
2605 | 139 | *out_mode = ret_mode; |
2606 | 139 | return TRUE; |
2607 | 139 | } |
2608 | | |
2609 | | /** |
2610 | | * ostree_repo_mode_from_string: |
2611 | | * @mode: a repo mode as a string |
2612 | | * @out_mode: (out): the corresponding #OstreeRepoMode |
2613 | | * @error: a #GError if the string is not a valid mode |
2614 | | */ |
2615 | | gboolean |
2616 | | ostree_repo_mode_from_string (const char *mode, OstreeRepoMode *out_mode, GError **error) |
2617 | 228 | { |
2618 | 228 | OstreeRepoMode ret_mode; |
2619 | | |
2620 | 228 | if (strcmp (mode, "bare") == 0) |
2621 | 0 | ret_mode = OSTREE_REPO_MODE_BARE; |
2622 | 228 | else if (strcmp (mode, "bare-user") == 0) |
2623 | 0 | ret_mode = OSTREE_REPO_MODE_BARE_USER; |
2624 | 228 | else if (strcmp (mode, "bare-user-only") == 0) |
2625 | 0 | ret_mode = OSTREE_REPO_MODE_BARE_USER_ONLY; |
2626 | 228 | else if (strcmp (mode, "archive-z2") == 0 || strcmp (mode, "archive") == 0) |
2627 | 228 | ret_mode = OSTREE_REPO_MODE_ARCHIVE; |
2628 | 0 | else if (strcmp (mode, "bare-split-xattrs") == 0) |
2629 | 0 | ret_mode = OSTREE_REPO_MODE_BARE_SPLIT_XATTRS; |
2630 | 0 | else |
2631 | 0 | return glnx_throw (error, "Invalid mode '%s' in repository configuration", mode); |
2632 | | |
2633 | 228 | *out_mode = ret_mode; |
2634 | 228 | return TRUE; |
2635 | 228 | } |
2636 | | |
2637 | | #define DEFAULT_CONFIG_CONTENTS \ |
2638 | 139 | ("[core]\n" \ |
2639 | 139 | "repo_version=1\n") |
2640 | | |
2641 | | /* Just write the dirs to disk, return a dfd */ |
2642 | | static gboolean |
2643 | | repo_create_at_internal (int dfd, const char *path, OstreeRepoMode mode, GVariant *options, |
2644 | | int *out_dfd, GCancellable *cancellable, GError **error) |
2645 | 139 | { |
2646 | 139 | GLNX_AUTO_PREFIX_ERROR ("Creating repo", error); |
2647 | 139 | struct stat stbuf; |
2648 | | /* We do objects/ last - if it exists we do nothing and exit successfully */ |
2649 | 139 | const char *state_dirs[] = { "tmp", "extensions", "state", "refs", |
2650 | 139 | "refs/heads", "refs/mirrors", "refs/remotes", "objects" }; |
2651 | | |
2652 | | /* Early return if we have an existing repo */ |
2653 | 139 | { |
2654 | 139 | g_autofree char *objects_path = g_build_filename (path, "objects", NULL); |
2655 | | |
2656 | 139 | if (!glnx_fstatat_allow_noent (dfd, objects_path, &stbuf, 0, error)) |
2657 | 0 | return FALSE; |
2658 | 139 | if (errno == 0) |
2659 | 0 | { |
2660 | 0 | glnx_autofd int repo_dfd = -1; |
2661 | 0 | if (!glnx_opendirat (dfd, path, TRUE, &repo_dfd, error)) |
2662 | 0 | return FALSE; |
2663 | | |
2664 | | /* Note early return */ |
2665 | 0 | *out_dfd = g_steal_fd (&repo_dfd); |
2666 | 0 | return TRUE; |
2667 | 0 | } |
2668 | 139 | } |
2669 | | |
2670 | 139 | if (mkdirat (dfd, path, DEFAULT_DIRECTORY_MODE) != 0) |
2671 | 139 | { |
2672 | 139 | if (G_UNLIKELY (errno != EEXIST)) |
2673 | 0 | return glnx_throw_errno_prefix (error, "mkdirat"); |
2674 | 139 | } |
2675 | | |
2676 | 139 | glnx_autofd int repo_dfd = -1; |
2677 | 139 | if (!glnx_opendirat (dfd, path, TRUE, &repo_dfd, error)) |
2678 | 0 | return FALSE; |
2679 | | |
2680 | 139 | if (!glnx_fstatat_allow_noent (repo_dfd, "config", &stbuf, 0, error)) |
2681 | 0 | return FALSE; |
2682 | 139 | if (errno == ENOENT) |
2683 | 139 | { |
2684 | 139 | const char *mode_str = NULL; |
2685 | 139 | g_autoptr (GString) config_data = g_string_new (DEFAULT_CONFIG_CONTENTS); |
2686 | | |
2687 | 139 | if (!ostree_repo_mode_to_string (mode, &mode_str, error)) |
2688 | 0 | return FALSE; |
2689 | 139 | g_assert (mode_str); |
2690 | | |
2691 | 139 | g_string_append_printf (config_data, "mode=%s\n", mode_str); |
2692 | | |
2693 | 139 | const char *collection_id = NULL; |
2694 | 139 | if (options) |
2695 | 0 | (void)g_variant_lookup (options, "collection-id", "&s", &collection_id); |
2696 | 139 | if (collection_id != NULL) |
2697 | 0 | g_string_append_printf (config_data, "collection-id=%s\n", collection_id); |
2698 | | |
2699 | 139 | if (!glnx_file_replace_contents_at (repo_dfd, "config", (guint8 *)config_data->str, |
2700 | 139 | config_data->len, 0, cancellable, error)) |
2701 | 0 | return FALSE; |
2702 | 139 | } |
2703 | | |
2704 | 1.25k | for (guint i = 0; i < G_N_ELEMENTS (state_dirs); i++) |
2705 | 1.11k | { |
2706 | 1.11k | const char *elt = state_dirs[i]; |
2707 | 1.11k | if (mkdirat (repo_dfd, elt, DEFAULT_DIRECTORY_MODE) == -1) |
2708 | 0 | { |
2709 | 0 | if (G_UNLIKELY (errno != EEXIST)) |
2710 | 0 | return glnx_throw_errno_prefix (error, "mkdirat"); |
2711 | 0 | } |
2712 | 1.11k | } |
2713 | | |
2714 | | /* Test that the fs supports user xattrs now, so we get an error early rather |
2715 | | * than during an object write later. |
2716 | | */ |
2717 | 139 | if (mode == OSTREE_REPO_MODE_BARE_USER) |
2718 | 0 | { |
2719 | 0 | g_auto (GLnxTmpfile) tmpf = { |
2720 | 0 | 0, |
2721 | 0 | }; |
2722 | |
|
2723 | 0 | if (!glnx_open_tmpfile_linkable_at (repo_dfd, ".", O_RDWR | O_CLOEXEC, &tmpf, error)) |
2724 | 0 | return FALSE; |
2725 | 0 | if (!_ostree_write_bareuser_metadata (tmpf.fd, 0, 0, 644, NULL, error)) |
2726 | 0 | return FALSE; |
2727 | 0 | } |
2728 | | |
2729 | 139 | *out_dfd = g_steal_fd (&repo_dfd); |
2730 | 139 | return TRUE; |
2731 | 139 | } |
2732 | | |
2733 | | /** |
2734 | | * ostree_repo_create: |
2735 | | * @self: An #OstreeRepo |
2736 | | * @mode: The mode to store the repository in |
2737 | | * @cancellable: Cancellable |
2738 | | * @error: Error |
2739 | | * |
2740 | | * Create the underlying structure on disk for the repository, and call |
2741 | | * ostree_repo_open() on the result, preparing it for use. |
2742 | | |
2743 | | * Since version 2016.8, this function will succeed on an existing |
2744 | | * repository, and finish creating any necessary files in a partially |
2745 | | * created repository. However, this function cannot change the mode |
2746 | | * of an existing repository, and will silently ignore an attempt to |
2747 | | * do so. |
2748 | | * |
2749 | | * Since 2017.9, "existing repository" is defined by the existence of an |
2750 | | * `objects` subdirectory. |
2751 | | * |
2752 | | * This function predates ostree_repo_create_at(). It is an error to call |
2753 | | * this function on a repository initialized via ostree_repo_open_at(). |
2754 | | */ |
2755 | | gboolean |
2756 | | ostree_repo_create (OstreeRepo *self, OstreeRepoMode mode, GCancellable *cancellable, |
2757 | | GError **error) |
2758 | 0 | { |
2759 | 0 | g_return_val_if_fail (self->repodir, FALSE); |
2760 | 0 | const char *repopath = gs_file_get_path_cached (self->repodir); |
2761 | 0 | g_autoptr (GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); |
2762 | 0 | if (self->collection_id) |
2763 | 0 | g_variant_builder_add (builder, "{s@v}", "collection-id", |
2764 | 0 | g_variant_new_variant (g_variant_new_string (self->collection_id))); |
2765 | |
|
2766 | 0 | glnx_autofd int repo_dir_fd = -1; |
2767 | 0 | g_autoptr (GVariant) options = g_variant_ref_sink (g_variant_builder_end (builder)); |
2768 | 0 | if (!repo_create_at_internal (AT_FDCWD, repopath, mode, options, &repo_dir_fd, cancellable, |
2769 | 0 | error)) |
2770 | 0 | return FALSE; |
2771 | 0 | self->repo_dir_fd = g_steal_fd (&repo_dir_fd); |
2772 | 0 | if (!ostree_repo_open (self, cancellable, error)) |
2773 | 0 | return FALSE; |
2774 | 0 | return TRUE; |
2775 | 0 | } |
2776 | | |
2777 | | /** |
2778 | | * ostree_repo_create_at: |
2779 | | * @dfd: Directory fd |
2780 | | * @path: Path |
2781 | | * @mode: The mode to store the repository in |
2782 | | * @options: (nullable): a{sv}: See below for accepted keys |
2783 | | * @cancellable: Cancellable |
2784 | | * @error: Error |
2785 | | * |
2786 | | * This is a file-descriptor relative version of ostree_repo_create(). |
2787 | | * Create the underlying structure on disk for the repository, and call |
2788 | | * ostree_repo_open_at() on the result, preparing it for use. |
2789 | | * |
2790 | | * If a repository already exists at @dfd + @path (defined by an `objects/` |
2791 | | * subdirectory existing), then this function will simply call |
2792 | | * ostree_repo_open_at(). In other words, this function cannot be used to change |
2793 | | * the mode or configuration (`repo/config`) of an existing repo. |
2794 | | * |
2795 | | * The @options dict may contain: |
2796 | | * |
2797 | | * - collection-id: s: Set as collection ID in repo/config (Since 2017.9) |
2798 | | * |
2799 | | * Returns: (transfer full): A new OSTree repository reference |
2800 | | * |
2801 | | * Since: 2017.10 |
2802 | | */ |
2803 | | OstreeRepo * |
2804 | | ostree_repo_create_at (int dfd, const char *path, OstreeRepoMode mode, GVariant *options, |
2805 | | GCancellable *cancellable, GError **error) |
2806 | 139 | { |
2807 | 139 | glnx_autofd int repo_dfd = -1; |
2808 | 139 | if (!repo_create_at_internal (dfd, path, mode, options, &repo_dfd, cancellable, error)) |
2809 | 0 | return NULL; |
2810 | 139 | return repo_open_at_take_fd (&repo_dfd, cancellable, error); |
2811 | 139 | } |
2812 | | |
2813 | | static gboolean |
2814 | | enumerate_directory_allow_noent (GFile *dirpath, const char *queryargs, |
2815 | | GFileQueryInfoFlags queryflags, GFileEnumerator **out_direnum, |
2816 | | GCancellable *cancellable, GError **error) |
2817 | 0 | { |
2818 | 0 | g_autoptr (GError) temp_error = NULL; |
2819 | 0 | g_autoptr (GFileEnumerator) ret_direnum = NULL; |
2820 | |
|
2821 | 0 | ret_direnum |
2822 | 0 | = g_file_enumerate_children (dirpath, queryargs, queryflags, cancellable, &temp_error); |
2823 | 0 | if (!ret_direnum) |
2824 | 0 | { |
2825 | 0 | if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) |
2826 | 0 | g_clear_error (&temp_error); |
2827 | 0 | else |
2828 | 0 | { |
2829 | 0 | g_propagate_error (error, g_steal_pointer (&temp_error)); |
2830 | 0 | return FALSE; |
2831 | 0 | } |
2832 | 0 | } |
2833 | | |
2834 | 0 | if (out_direnum) |
2835 | 0 | *out_direnum = g_steal_pointer (&ret_direnum); |
2836 | 0 | return TRUE; |
2837 | 0 | } |
2838 | | |
2839 | | static gboolean |
2840 | | add_remotes_from_keyfile (OstreeRepo *self, GKeyFile *keyfile, GFile *file, GError **error) |
2841 | 228 | { |
2842 | 228 | GQueue queue = G_QUEUE_INIT; |
2843 | 228 | g_auto (GStrv) groups = NULL; |
2844 | 228 | gsize length, ii; |
2845 | 228 | gboolean ret = FALSE; |
2846 | | |
2847 | 228 | g_mutex_lock (&self->remotes_lock); |
2848 | | |
2849 | 228 | groups = g_key_file_get_groups (keyfile, &length); |
2850 | | |
2851 | 542 | for (ii = 0; ii < length; ii++) |
2852 | 314 | { |
2853 | 314 | OstreeRemote *remote; |
2854 | | |
2855 | 314 | remote = ostree_remote_new_from_keyfile (keyfile, groups[ii]); |
2856 | | |
2857 | 314 | if (remote != NULL) |
2858 | 7 | { |
2859 | | /* Make sure all the remotes in the key file are |
2860 | | * acceptable before adding any to the OstreeRepo. */ |
2861 | 7 | g_queue_push_tail (&queue, remote); |
2862 | | |
2863 | 7 | if (g_hash_table_contains (self->remotes, remote->name)) |
2864 | 0 | { |
2865 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, |
2866 | 0 | "Multiple specifications found for remote \"%s\"", remote->name); |
2867 | 0 | goto out; |
2868 | 0 | } |
2869 | | |
2870 | 7 | if (file != NULL) |
2871 | 0 | remote->file = g_object_ref (file); |
2872 | 7 | } |
2873 | 314 | } |
2874 | | |
2875 | 235 | while (!g_queue_is_empty (&queue)) |
2876 | 7 | { |
2877 | 7 | OstreeRemote *remote = g_queue_pop_head (&queue); |
2878 | 7 | g_hash_table_replace (self->remotes, remote->name, remote); |
2879 | 7 | } |
2880 | | |
2881 | 228 | ret = TRUE; |
2882 | | |
2883 | 228 | out: |
2884 | 228 | while (!g_queue_is_empty (&queue)) |
2885 | 0 | ostree_remote_unref (g_queue_pop_head (&queue)); |
2886 | | |
2887 | 228 | g_mutex_unlock (&self->remotes_lock); |
2888 | | |
2889 | 228 | return ret; |
2890 | 228 | } |
2891 | | |
2892 | | static gboolean |
2893 | | append_one_remote_config (OstreeRepo *self, GFile *path, GCancellable *cancellable, GError **error) |
2894 | 0 | { |
2895 | 0 | g_autoptr (GKeyFile) remotedata = g_key_file_new (); |
2896 | 0 | const char *pathname = gs_file_get_path_cached (path); |
2897 | 0 | if (!g_key_file_load_from_file (remotedata, pathname, 0, error)) |
2898 | 0 | return glnx_prefix_error (error, "Failed to parse %s", pathname); |
2899 | | |
2900 | 0 | return add_remotes_from_keyfile (self, remotedata, path, error); |
2901 | 0 | } |
2902 | | |
2903 | | static GFile * |
2904 | | get_remotes_d_dir (OstreeRepo *self, GFile *sysroot) |
2905 | 228 | { |
2906 | 228 | g_autoptr (GFile) sysroot_owned = NULL; |
2907 | | /* Very complicated sysroot logic; this bit breaks the otherwise mostly clean |
2908 | | * layering between OstreeRepo and OstreeSysroot. First, If a sysroot was |
2909 | | * provided, use it. Otherwise, check to see whether we reference |
2910 | | * /ostree/repo, or if not that, see if we have a ref to a sysroot (and it's |
2911 | | * physical). |
2912 | | */ |
2913 | 228 | g_autoptr (OstreeSysroot) sysroot_ref = NULL; |
2914 | 228 | if (sysroot == NULL) |
2915 | 228 | { |
2916 | | /* No explicit sysroot? Let's see if we have a kind */ |
2917 | 228 | switch (self->sysroot_kind) |
2918 | 228 | { |
2919 | 0 | case OSTREE_REPO_SYSROOT_KIND_UNKNOWN: |
2920 | 0 | g_assert_not_reached (); |
2921 | 0 | break; |
2922 | 228 | case OSTREE_REPO_SYSROOT_KIND_NO: |
2923 | 228 | break; |
2924 | 0 | case OSTREE_REPO_SYSROOT_KIND_IS_SYSROOT_OSTREE: |
2925 | 0 | sysroot = sysroot_owned = g_file_new_for_path ("/"); |
2926 | 0 | break; |
2927 | 0 | case OSTREE_REPO_SYSROOT_KIND_VIA_SYSROOT: |
2928 | 0 | sysroot_ref = (OstreeSysroot *)g_weak_ref_get (&self->sysroot); |
2929 | | /* Only write to /etc/ostree/remotes.d if we are pointed at a deployment */ |
2930 | 0 | if (sysroot_ref != NULL && !sysroot_ref->is_physical) |
2931 | 0 | sysroot = ostree_sysroot_get_path (sysroot_ref); |
2932 | 0 | break; |
2933 | 228 | } |
2934 | 228 | } |
2935 | | |
2936 | 228 | (void)sysroot_owned; // Conditionally owned |
2937 | | |
2938 | | /* For backwards compat, also fall back to the sysroot-path variable, which we |
2939 | | * don't set anymore internally, and I hope no one else uses. |
2940 | | */ |
2941 | 228 | if (sysroot == NULL && sysroot_ref == NULL) |
2942 | 228 | sysroot = self->sysroot_dir; |
2943 | | |
2944 | | /* Was the config directory specified? If so, use that with the |
2945 | | * optional sysroot prepended. If not, return the path in /etc if the |
2946 | | * sysroot was found and NULL otherwise to use the repo config. |
2947 | | */ |
2948 | 228 | if (self->remotes_config_dir != NULL) |
2949 | 0 | { |
2950 | 0 | if (sysroot == NULL) |
2951 | 0 | return g_file_new_for_path (self->remotes_config_dir); |
2952 | 0 | else |
2953 | 0 | return g_file_resolve_relative_path (sysroot, self->remotes_config_dir); |
2954 | 0 | } |
2955 | 228 | else if (sysroot == NULL) |
2956 | 228 | return NULL; |
2957 | 0 | else |
2958 | 0 | return g_file_resolve_relative_path (sysroot, SYSCONF_REMOTES); |
2959 | 228 | } |
2960 | | |
2961 | | static gboolean |
2962 | | min_free_space_calculate_reserved_bytes (OstreeRepo *self, guint64 *bytes, GError **error) |
2963 | 89 | { |
2964 | 89 | guint64 reserved_bytes = 0; |
2965 | | |
2966 | 89 | struct statvfs stvfsbuf; |
2967 | 89 | if (TEMP_FAILURE_RETRY (fstatvfs (self->repo_dir_fd, &stvfsbuf)) < 0) |
2968 | 0 | return glnx_throw_errno_prefix (error, "fstatvfs"); |
2969 | | |
2970 | 89 | if (self->min_free_space_mb > 0) |
2971 | 0 | { |
2972 | 0 | if (self->min_free_space_mb > (G_MAXUINT64 >> 20)) |
2973 | 0 | return glnx_throw ( |
2974 | 0 | error, |
2975 | 0 | "min-free-space value is greater than the maximum allowed value of %" G_GUINT64_FORMAT |
2976 | 0 | " bytes", |
2977 | 0 | (G_MAXUINT64 >> 20)); |
2978 | | |
2979 | 0 | reserved_bytes = self->min_free_space_mb << 20; |
2980 | 0 | } |
2981 | 89 | else if (self->min_free_space_percent > 0) |
2982 | 89 | { |
2983 | 89 | if (stvfsbuf.f_frsize > (G_MAXUINT64 / stvfsbuf.f_blocks)) |
2984 | 0 | return glnx_throw ( |
2985 | 0 | error, |
2986 | 0 | "Filesystem's size is greater than the maximum allowed value of %" G_GUINT64_FORMAT |
2987 | 0 | " bytes", |
2988 | 0 | (G_MAXUINT64 / stvfsbuf.f_blocks)); |
2989 | | |
2990 | 89 | guint64 total_bytes = (stvfsbuf.f_frsize * stvfsbuf.f_blocks); |
2991 | 89 | reserved_bytes = ((double)total_bytes) * (self->min_free_space_percent / 100.0); |
2992 | 89 | } |
2993 | | |
2994 | 89 | *bytes = reserved_bytes; |
2995 | 89 | return TRUE; |
2996 | 89 | } |
2997 | | |
2998 | | static gboolean |
2999 | | min_free_space_size_validate_and_convert (OstreeRepo *self, const char *min_free_space_size_str, |
3000 | | GError **error) |
3001 | 0 | { |
3002 | 0 | static GRegex *regex; |
3003 | 0 | static gsize regex_initialized; |
3004 | 0 | if (g_once_init_enter (®ex_initialized)) |
3005 | 0 | { |
3006 | 0 | regex = g_regex_new ("^([0-9]+)(G|M|T)B$", 0, 0, NULL); |
3007 | 0 | g_assert (regex); |
3008 | 0 | g_once_init_leave (®ex_initialized, 1); |
3009 | 0 | } |
3010 | | |
3011 | 0 | g_autoptr (GMatchInfo) match = NULL; |
3012 | 0 | if (!g_regex_match (regex, min_free_space_size_str, 0, &match)) |
3013 | 0 | return glnx_throw (error, "It should be of the format '123MB', '123GB' or '123TB'"); |
3014 | | |
3015 | 0 | g_autofree char *size_str = g_match_info_fetch (match, 1); |
3016 | 0 | g_autofree char *unit = g_match_info_fetch (match, 2); |
3017 | 0 | guint shifts; |
3018 | |
|
3019 | 0 | switch (*unit) |
3020 | 0 | { |
3021 | 0 | case 'M': |
3022 | 0 | shifts = 0; |
3023 | 0 | break; |
3024 | 0 | case 'G': |
3025 | 0 | shifts = 10; |
3026 | 0 | break; |
3027 | 0 | case 'T': |
3028 | 0 | shifts = 20; |
3029 | 0 | break; |
3030 | 0 | default: |
3031 | 0 | g_assert_not_reached (); |
3032 | 0 | } |
3033 | | |
3034 | 0 | guint64 min_free_space = g_ascii_strtoull (size_str, NULL, 10); |
3035 | 0 | if (shifts > 0 && g_bit_nth_lsf (min_free_space, 63 - shifts) != -1) |
3036 | 0 | return glnx_throw (error, "Value was too high"); |
3037 | | |
3038 | 0 | self->min_free_space_mb = min_free_space << shifts; |
3039 | |
|
3040 | 0 | return TRUE; |
3041 | 0 | } |
3042 | | |
3043 | | static gboolean |
3044 | | reload_core_config (OstreeRepo *self, GCancellable *cancellable, GError **error) |
3045 | 228 | { |
3046 | 228 | g_autofree char *version = NULL; |
3047 | 228 | g_autofree char *mode = NULL; |
3048 | 228 | g_autofree char *contents = NULL; |
3049 | 228 | g_autofree char *parent_repo_path = NULL; |
3050 | 228 | gboolean is_archive; |
3051 | | |
3052 | 228 | version = g_key_file_get_value (self->config, "core", "repo_version", error); |
3053 | 228 | if (!version) |
3054 | 0 | return FALSE; |
3055 | | |
3056 | 228 | if (strcmp (version, "1") != 0) |
3057 | 0 | return glnx_throw (error, "Invalid repository version '%s'", version); |
3058 | | |
3059 | 228 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "archive", FALSE, &is_archive, |
3060 | 228 | error)) |
3061 | 0 | return FALSE; |
3062 | 228 | if (is_archive) |
3063 | 0 | { |
3064 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, |
3065 | 0 | "This version of OSTree no longer supports \"archive\" repositories; use " |
3066 | 0 | "archive-z2 instead"); |
3067 | 0 | return FALSE; |
3068 | 0 | } |
3069 | | |
3070 | 228 | if (!ot_keyfile_get_value_with_default (self->config, "core", "mode", "bare", &mode, error)) |
3071 | 0 | return FALSE; |
3072 | 228 | if (!ostree_repo_mode_from_string (mode, &self->mode, error)) |
3073 | 0 | return FALSE; |
3074 | | |
3075 | 228 | if (self->writable) |
3076 | 228 | { |
3077 | 228 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "enable-uncompressed-cache", |
3078 | 228 | TRUE, &self->enable_uncompressed_cache, error)) |
3079 | 0 | return FALSE; |
3080 | 228 | } |
3081 | 0 | else |
3082 | 0 | self->enable_uncompressed_cache = FALSE; |
3083 | | |
3084 | 228 | { |
3085 | 228 | gboolean do_fsync; |
3086 | | |
3087 | 228 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "fsync", TRUE, &do_fsync, |
3088 | 228 | error)) |
3089 | 0 | return FALSE; |
3090 | | |
3091 | 228 | if (!do_fsync) |
3092 | 0 | ostree_repo_set_disable_fsync (self, TRUE); |
3093 | 228 | } |
3094 | | |
3095 | 228 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "per-object-fsync", FALSE, |
3096 | 228 | &self->per_object_fsync, error)) |
3097 | 0 | return FALSE; |
3098 | | |
3099 | | /* See https://github.com/ostreedev/ostree/issues/758 */ |
3100 | 228 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "disable-xattrs", FALSE, |
3101 | 228 | &self->disable_xattrs, error)) |
3102 | 0 | return FALSE; |
3103 | | |
3104 | 228 | { |
3105 | 228 | g_autofree char *tmp_expiry_seconds = NULL; |
3106 | | |
3107 | | /* 86400 secs = one day */ |
3108 | 228 | if (!ot_keyfile_get_value_with_default (self->config, "core", "tmp-expiry-secs", "86400", |
3109 | 228 | &tmp_expiry_seconds, error)) |
3110 | 0 | return FALSE; |
3111 | | |
3112 | 228 | self->tmp_expiry_seconds = g_ascii_strtoull (tmp_expiry_seconds, NULL, 10); |
3113 | 228 | } |
3114 | | |
3115 | 0 | { |
3116 | 228 | gboolean locking; |
3117 | | /* Enabled by default in 2018.05 */ |
3118 | 228 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "locking", TRUE, &locking, |
3119 | 228 | error)) |
3120 | 0 | return FALSE; |
3121 | 228 | if (!locking) |
3122 | 0 | { |
3123 | 0 | self->lock_timeout_seconds = REPO_LOCK_DISABLED; |
3124 | 0 | } |
3125 | 228 | else |
3126 | 228 | { |
3127 | 228 | g_autofree char *lock_timeout_seconds = NULL; |
3128 | | |
3129 | 228 | if (!ot_keyfile_get_value_with_default (self->config, "core", "lock-timeout-secs", "300", |
3130 | 228 | &lock_timeout_seconds, error)) |
3131 | 0 | return FALSE; |
3132 | | |
3133 | 228 | self->lock_timeout_seconds = g_ascii_strtoll (lock_timeout_seconds, NULL, 10); |
3134 | 228 | } |
3135 | 228 | } |
3136 | | |
3137 | 228 | { |
3138 | 228 | g_autofree char *compression_level_str = NULL; |
3139 | | |
3140 | | /* gzip defaults to 6 */ |
3141 | 228 | (void)ot_keyfile_get_value_with_default (self->config, "archive", "zlib-level", NULL, |
3142 | 228 | &compression_level_str, NULL); |
3143 | | |
3144 | 228 | if (compression_level_str) |
3145 | | /* Ensure level is in [1,9] */ |
3146 | 0 | self->zlib_compression_level |
3147 | 0 | = MAX (1, MIN (9, g_ascii_strtoull (compression_level_str, NULL, 10))); |
3148 | 228 | else |
3149 | 228 | self->zlib_compression_level = OSTREE_ARCHIVE_DEFAULT_COMPRESSION_LEVEL; |
3150 | 228 | } |
3151 | | |
3152 | 228 | { |
3153 | | /* Try to parse both min-free-space-* config options first. If both are absent, fallback on 3% |
3154 | | * free space. If both are present and are non-zero, use min-free-space-size unconditionally |
3155 | | * and display a warning. |
3156 | | */ |
3157 | 228 | if (g_key_file_has_key (self->config, "core", "min-free-space-size", NULL)) |
3158 | 0 | { |
3159 | 0 | g_autofree char *min_free_space_size_str = NULL; |
3160 | |
|
3161 | 0 | if (!ot_keyfile_get_value_with_default (self->config, "core", "min-free-space-size", NULL, |
3162 | 0 | &min_free_space_size_str, error)) |
3163 | 0 | return FALSE; |
3164 | | |
3165 | | /* Validate the string and convert the size to MBs */ |
3166 | 0 | if (!min_free_space_size_validate_and_convert (self, min_free_space_size_str, error)) |
3167 | 0 | return glnx_prefix_error (error, "Invalid min-free-space-size '%s'", |
3168 | 0 | min_free_space_size_str); |
3169 | 0 | } |
3170 | | |
3171 | 228 | if (g_key_file_has_key (self->config, "core", "min-free-space-percent", NULL)) |
3172 | 0 | { |
3173 | 0 | g_autofree char *min_free_space_percent_str = NULL; |
3174 | |
|
3175 | 0 | if (!ot_keyfile_get_value_with_default (self->config, "core", "min-free-space-percent", |
3176 | 0 | NULL, &min_free_space_percent_str, error)) |
3177 | 0 | return FALSE; |
3178 | | |
3179 | 0 | self->min_free_space_percent = g_ascii_strtoull (min_free_space_percent_str, NULL, 10); |
3180 | 0 | if (self->min_free_space_percent > 99) |
3181 | 0 | return glnx_throw (error, "Invalid min-free-space-percent '%s'", |
3182 | 0 | min_free_space_percent_str); |
3183 | 0 | } |
3184 | 228 | else if (!g_key_file_has_key (self->config, "core", "min-free-space-size", NULL)) |
3185 | 228 | { |
3186 | | /* Default fallback of 3% free space. If changing this, be sure to change the man page too |
3187 | | */ |
3188 | 228 | self->min_free_space_percent = 3; |
3189 | 228 | self->min_free_space_mb = 0; |
3190 | 228 | } |
3191 | | |
3192 | 228 | if (self->min_free_space_percent != 0 && self->min_free_space_mb != 0) |
3193 | 0 | { |
3194 | 0 | self->min_free_space_percent = 0; |
3195 | 0 | g_debug ("Both min-free-space-percent and -size are mentioned in config. Enforcing " |
3196 | 0 | "min-free-space-size check only."); |
3197 | 0 | } |
3198 | 228 | } |
3199 | | |
3200 | 228 | if (!_ostree_repo_parse_fsverity_config (self, error)) |
3201 | 0 | return FALSE; |
3202 | | |
3203 | 228 | if (!_ostree_repo_parse_composefs_config (self, error)) |
3204 | 0 | return FALSE; |
3205 | | |
3206 | 228 | { |
3207 | 228 | g_clear_pointer (&self->collection_id, g_free); |
3208 | 228 | if (!ot_keyfile_get_value_with_default (self->config, "core", "collection-id", NULL, |
3209 | 228 | &self->collection_id, NULL)) |
3210 | 0 | return FALSE; |
3211 | 228 | } |
3212 | | |
3213 | 228 | if (!ot_keyfile_get_value_with_default (self->config, "core", "parent", NULL, &parent_repo_path, |
3214 | 228 | error)) |
3215 | 0 | return FALSE; |
3216 | | |
3217 | 228 | if (parent_repo_path && parent_repo_path[0]) |
3218 | 0 | { |
3219 | 0 | g_autoptr (GFile) parent_repo_f = g_file_new_for_path (parent_repo_path); |
3220 | |
|
3221 | 0 | g_clear_object (&self->parent_repo); |
3222 | 0 | self->parent_repo = ostree_repo_new (parent_repo_f); |
3223 | |
|
3224 | 0 | if (!ostree_repo_open (self->parent_repo, cancellable, error)) |
3225 | 0 | { |
3226 | 0 | g_prefix_error (error, "While checking parent repository '%s': ", |
3227 | 0 | gs_file_get_path_cached (parent_repo_f)); |
3228 | 0 | return FALSE; |
3229 | 0 | } |
3230 | 0 | } |
3231 | | |
3232 | | /* By default, only add remotes in a remotes config directory for |
3233 | | * system repos. This is to preserve legacy behavior for non-system |
3234 | | * repos that specify a remotes config dir (flatpak). |
3235 | | */ |
3236 | 228 | { |
3237 | 228 | gboolean is_system = ostree_repo_is_system (self); |
3238 | | |
3239 | 228 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "add-remotes-config-dir", |
3240 | 228 | is_system, &self->add_remotes_config_dir, error)) |
3241 | 0 | return FALSE; |
3242 | 228 | } |
3243 | | |
3244 | 228 | { |
3245 | 228 | g_autofree char *payload_threshold = NULL; |
3246 | | |
3247 | 228 | if (!ot_keyfile_get_value_with_default (self->config, "core", "payload-link-threshold", "-1", |
3248 | 228 | &payload_threshold, error)) |
3249 | 0 | return FALSE; |
3250 | | |
3251 | 228 | self->payload_link_threshold = g_ascii_strtoull (payload_threshold, NULL, 10); |
3252 | 228 | } |
3253 | | |
3254 | 0 | { |
3255 | 228 | g_auto (GStrv) configured_finders = NULL; |
3256 | 228 | g_autoptr (GError) local_error = NULL; |
3257 | | |
3258 | 228 | configured_finders = g_key_file_get_string_list (self->config, "core", "default-repo-finders", |
3259 | 228 | NULL, &local_error); |
3260 | 228 | if (g_error_matches (local_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) |
3261 | 228 | g_clear_error (&local_error); |
3262 | 0 | else if (local_error != NULL) |
3263 | 0 | { |
3264 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
3265 | 0 | return FALSE; |
3266 | 0 | } |
3267 | | |
3268 | 228 | if (configured_finders != NULL && *configured_finders == NULL) |
3269 | 0 | return glnx_throw (error, "Invalid empty default-repo-finders configuration"); |
3270 | | |
3271 | 228 | for (char **iter = configured_finders; iter && *iter; iter++) |
3272 | 0 | { |
3273 | 0 | const char *repo_finder = *iter; |
3274 | |
|
3275 | 0 | if (strcmp (repo_finder, "config") != 0 && strcmp (repo_finder, "lan") != 0 |
3276 | 0 | && strcmp (repo_finder, "mount") != 0) |
3277 | 0 | return glnx_throw (error, "Invalid configured repo-finder '%s'", repo_finder); |
3278 | 0 | } |
3279 | | |
3280 | | /* Fall back to a default set of finders */ |
3281 | 228 | if (configured_finders == NULL) |
3282 | 228 | configured_finders = g_strsplit ("config;mount", ";", -1); |
3283 | | |
3284 | 228 | g_clear_pointer (&self->repo_finders, g_strfreev); |
3285 | 228 | self->repo_finders = g_steal_pointer (&configured_finders); |
3286 | 228 | } |
3287 | | |
3288 | 228 | return TRUE; |
3289 | 228 | } |
3290 | | |
3291 | | static gboolean |
3292 | | reload_remote_config (OstreeRepo *self, GCancellable *cancellable, GError **error) |
3293 | 228 | { |
3294 | 228 | GLNX_AUTO_PREFIX_ERROR ("Reloading remotes", error); |
3295 | | |
3296 | 228 | g_mutex_lock (&self->remotes_lock); |
3297 | 228 | g_hash_table_remove_all (self->remotes); |
3298 | 228 | g_mutex_unlock (&self->remotes_lock); |
3299 | | |
3300 | 228 | if (!add_remotes_from_keyfile (self, self->config, NULL, error)) |
3301 | 0 | return FALSE; |
3302 | | |
3303 | 228 | g_autoptr (GFile) remotes_d = get_remotes_d_dir (self, NULL); |
3304 | 228 | if (remotes_d == NULL) |
3305 | 228 | return TRUE; |
3306 | | |
3307 | 0 | g_autoptr (GFileEnumerator) direnum = NULL; |
3308 | 0 | if (!enumerate_directory_allow_noent (remotes_d, OSTREE_GIO_FAST_QUERYINFO, 0, &direnum, |
3309 | 0 | cancellable, error)) |
3310 | 0 | return FALSE; |
3311 | 0 | if (direnum) |
3312 | 0 | { |
3313 | 0 | while (TRUE) |
3314 | 0 | { |
3315 | 0 | GFileInfo *file_info; |
3316 | 0 | GFile *path; |
3317 | 0 | const char *name; |
3318 | 0 | guint32 type; |
3319 | |
|
3320 | 0 | if (!g_file_enumerator_iterate (direnum, &file_info, &path, NULL, error)) |
3321 | 0 | return FALSE; |
3322 | 0 | if (file_info == NULL) |
3323 | 0 | break; |
3324 | | |
3325 | 0 | name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); |
3326 | 0 | type = g_file_info_get_attribute_uint32 (file_info, "standard::type"); |
3327 | |
|
3328 | 0 | if (type == G_FILE_TYPE_REGULAR && g_str_has_suffix (name, ".conf")) |
3329 | 0 | { |
3330 | 0 | if (!append_one_remote_config (self, path, cancellable, error)) |
3331 | 0 | return FALSE; |
3332 | 0 | } |
3333 | 0 | } |
3334 | 0 | } |
3335 | | |
3336 | 0 | return TRUE; |
3337 | 0 | } |
3338 | | |
3339 | | static gboolean |
3340 | | reload_sysroot_config (OstreeRepo *self, GCancellable *cancellable, GError **error) |
3341 | 228 | { |
3342 | 228 | g_autofree char *boot_counting_str = NULL; |
3343 | | |
3344 | 228 | if (!ot_keyfile_get_value_with_default_group_optional ( |
3345 | 228 | self->config, "sysroot", "boot-counting-tries", "0", &boot_counting_str, error)) |
3346 | 0 | return FALSE; |
3347 | 228 | guint64 v; |
3348 | 228 | if (!g_ascii_string_to_unsigned (boot_counting_str, 10, 0, 5, &v, error)) |
3349 | 0 | return glnx_prefix_error (error, "Parsing sysroot.boot-counting-tries"); |
3350 | 228 | self->boot_counting = (guint)v; |
3351 | | |
3352 | 228 | g_autofree char *bootloader = NULL; |
3353 | | |
3354 | 228 | if (!ot_keyfile_get_value_with_default_group_optional (self->config, "sysroot", "bootloader", |
3355 | 228 | CFG_SYSROOT_BOOTLOADER_DEFAULT_STR, |
3356 | 228 | &bootloader, error)) |
3357 | 0 | return FALSE; |
3358 | | |
3359 | | /* TODO: possibly later add support for specifying a generic bootloader |
3360 | | * binary "x" in /usr/lib/ostree/bootloaders/x). See: |
3361 | | * https://github.com/ostreedev/ostree/issues/1719 |
3362 | | * https://github.com/ostreedev/ostree/issues/1801 |
3363 | | */ |
3364 | 228 | gboolean valid_bootloader = FALSE; |
3365 | 1.82k | for (int i = 0; CFG_SYSROOT_BOOTLOADER_OPTS_STR[i]; i++) |
3366 | 1.59k | { |
3367 | 1.59k | if (g_str_equal (bootloader, CFG_SYSROOT_BOOTLOADER_OPTS_STR[i])) |
3368 | 228 | { |
3369 | 228 | self->bootloader = (OstreeCfgSysrootBootloaderOpt)i; |
3370 | 228 | valid_bootloader = TRUE; |
3371 | 228 | } |
3372 | 1.59k | } |
3373 | 228 | if (!valid_bootloader) |
3374 | 0 | { |
3375 | 0 | return glnx_throw (error, "Invalid bootloader configuration: '%s'", bootloader); |
3376 | 0 | } |
3377 | | /* Parse bls-append-except-default string list. */ |
3378 | 228 | g_auto (GStrv) read_values = NULL; |
3379 | 228 | if (!ot_keyfile_get_string_list_with_default ( |
3380 | 228 | self->config, "sysroot", "bls-append-except-default", ';', NULL, &read_values, error)) |
3381 | 0 | return glnx_throw (error, "Unable to parse bls-append-except-default"); |
3382 | | |
3383 | | /* get all key value pairs in bls-append-except-default */ |
3384 | 228 | g_hash_table_remove_all (self->bls_append_values); |
3385 | 228 | for (char **iter = read_values; iter && *iter; iter++) |
3386 | 0 | { |
3387 | 0 | const char *key_value = *iter; |
3388 | 0 | const char *sep = strchr (key_value, '='); |
3389 | 0 | if (sep == NULL) |
3390 | 0 | { |
3391 | 0 | glnx_throw ( |
3392 | 0 | error, |
3393 | 0 | "bls-append-except-default key must be of the form \"key1=value1;key2=value2...\""); |
3394 | 0 | return FALSE; |
3395 | 0 | } |
3396 | 0 | char *key = g_strndup (key_value, sep - key_value); |
3397 | 0 | char *value = g_strdup (sep + 1); |
3398 | 0 | g_hash_table_replace (self->bls_append_values, key, value); |
3399 | 0 | } |
3400 | | |
3401 | 228 | if (!ot_keyfile_get_boolean_with_default (self->config, "sysroot", "bootprefix", FALSE, |
3402 | 228 | &self->enable_bootprefix, error)) |
3403 | 0 | return FALSE; |
3404 | | |
3405 | 228 | return TRUE; |
3406 | 228 | } |
3407 | | |
3408 | | static gboolean |
3409 | | reload_config_inner (OstreeRepo *self, GCancellable *cancellable, GError **error) |
3410 | 228 | { |
3411 | 228 | if (!reload_core_config (self, cancellable, error)) |
3412 | 0 | return FALSE; |
3413 | 228 | if (!reload_remote_config (self, cancellable, error)) |
3414 | 0 | return FALSE; |
3415 | 228 | if (!reload_sysroot_config (self, cancellable, error)) |
3416 | 0 | return FALSE; |
3417 | 228 | return TRUE; |
3418 | 228 | } |
3419 | | |
3420 | | /** |
3421 | | * ostree_repo_reload_config: |
3422 | | * @self: repo |
3423 | | * @cancellable: cancellable |
3424 | | * @error: error |
3425 | | * |
3426 | | * By default, an #OstreeRepo will cache the remote configuration and its |
3427 | | * own repo/config data. This API can be used to reload it. |
3428 | | * |
3429 | | * Since: 2017.2 |
3430 | | */ |
3431 | | gboolean |
3432 | | ostree_repo_reload_config (OstreeRepo *self, GCancellable *cancellable, GError **error) |
3433 | 278 | { |
3434 | 278 | g_clear_pointer (&self->config, g_key_file_unref); |
3435 | 278 | self->config = g_key_file_new (); |
3436 | | |
3437 | 278 | gsize len; |
3438 | 278 | g_autofree char *contents |
3439 | 278 | = glnx_file_get_contents_utf8_at (self->repo_dir_fd, "config", &len, NULL, error); |
3440 | 278 | if (!contents) |
3441 | 50 | return FALSE; |
3442 | 228 | if (!g_key_file_load_from_data (self->config, contents, len, 0, error)) |
3443 | 0 | { |
3444 | 0 | g_prefix_error (error, "Couldn't parse config file: "); |
3445 | 0 | return FALSE; |
3446 | 0 | } |
3447 | | |
3448 | 228 | return reload_config_inner (self, cancellable, error); |
3449 | 228 | } |
3450 | | |
3451 | | gboolean |
3452 | | ostree_repo_open (OstreeRepo *self, GCancellable *cancellable, GError **error) |
3453 | 139 | { |
3454 | 139 | GLNX_AUTO_PREFIX_ERROR ("opening repo", error); |
3455 | | |
3456 | 139 | struct stat stbuf; |
3457 | | |
3458 | 139 | g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
3459 | | |
3460 | 139 | if (self->inited) |
3461 | 0 | return TRUE; |
3462 | | |
3463 | | /* We use a directory of the form `staging-${BOOT_ID}-${RANDOM}` |
3464 | | * where if the ${BOOT_ID} doesn't match, we know file contents |
3465 | | * possibly haven't been sync'd to disk and need to be discarded. |
3466 | | */ |
3467 | 139 | { |
3468 | 139 | const char *env_bootid = getenv ("OSTREE_BOOTID"); |
3469 | 139 | g_autofree char *boot_id = NULL; |
3470 | | |
3471 | 139 | if (env_bootid != NULL) |
3472 | 0 | boot_id = g_strdup (env_bootid); |
3473 | 139 | else |
3474 | 139 | { |
3475 | 139 | if (!g_file_get_contents ("/proc/sys/kernel/random/boot_id", &boot_id, NULL, error)) |
3476 | 0 | return FALSE; |
3477 | 139 | g_strdelimit (boot_id, "\n", '\0'); |
3478 | 139 | } |
3479 | | |
3480 | 139 | self->stagedir_prefix = g_strconcat (OSTREE_REPO_TMPDIR_STAGING, boot_id, "-", NULL); |
3481 | 139 | } |
3482 | | |
3483 | 139 | if (self->repo_dir_fd == -1) |
3484 | 0 | { |
3485 | 0 | g_assert (self->repodir); |
3486 | 0 | if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (self->repodir), TRUE, |
3487 | 0 | &self->repo_dir_fd, error)) |
3488 | 0 | return FALSE; |
3489 | 0 | } |
3490 | | |
3491 | 139 | if (!glnx_fstat (self->repo_dir_fd, &stbuf, error)) |
3492 | 0 | return FALSE; |
3493 | 139 | self->device = stbuf.st_dev; |
3494 | 139 | self->inode = stbuf.st_ino; |
3495 | | |
3496 | 139 | if (!glnx_opendirat (self->repo_dir_fd, "objects", TRUE, &self->objects_dir_fd, error)) |
3497 | 0 | return FALSE; |
3498 | | |
3499 | 139 | self->writable = faccessat (self->objects_dir_fd, ".", W_OK, 0) == 0; |
3500 | 139 | if (!self->writable) |
3501 | 0 | { |
3502 | | /* This is returned through ostree_repo_is_writable(). */ |
3503 | 0 | glnx_set_error_from_errno (&self->writable_error); |
3504 | | /* Note - we don't return this error yet! */ |
3505 | 0 | } |
3506 | | |
3507 | 139 | { |
3508 | 139 | struct statfs fsstbuf; |
3509 | 139 | if (fstatfs (self->repo_dir_fd, &fsstbuf) < 0) |
3510 | 0 | return glnx_throw_errno_prefix (error, "fstatfs"); |
3511 | 139 | #ifndef FUSE_SUPER_MAGIC |
3512 | 139 | #define FUSE_SUPER_MAGIC 0x65735546 |
3513 | 139 | #endif |
3514 | 139 | self->is_on_fuse = (fsstbuf.f_type == FUSE_SUPER_MAGIC); |
3515 | 139 | g_debug ("using fuse: %d", self->is_on_fuse); |
3516 | 139 | } |
3517 | | |
3518 | 139 | if (!glnx_fstat (self->objects_dir_fd, &stbuf, error)) |
3519 | 0 | return FALSE; |
3520 | 139 | self->owner_uid = stbuf.st_uid; |
3521 | | |
3522 | 139 | if (self->writable) |
3523 | 139 | { |
3524 | | /* Always try to recreate the tmpdir to be nice to people |
3525 | | * who are looking to free up space. |
3526 | | * |
3527 | | * https://github.com/ostreedev/ostree/issues/1018 |
3528 | | */ |
3529 | 139 | if (mkdirat (self->repo_dir_fd, "tmp", DEFAULT_DIRECTORY_MODE) == -1) |
3530 | 139 | { |
3531 | 139 | if (G_UNLIKELY (errno != EEXIST)) |
3532 | 0 | return glnx_throw_errno_prefix (error, "mkdir(tmp)"); |
3533 | 139 | } |
3534 | 139 | } |
3535 | | |
3536 | 139 | if (!glnx_opendirat (self->repo_dir_fd, "tmp", TRUE, &self->tmp_dir_fd, error)) |
3537 | 0 | return FALSE; |
3538 | | |
3539 | 139 | if (self->writable && getenv ("OSTREE_SKIP_CACHE") == NULL) |
3540 | 139 | { |
3541 | 139 | if (!glnx_shutil_mkdir_p_at (self->tmp_dir_fd, _OSTREE_CACHE_DIR, DEFAULT_DIRECTORY_MODE, |
3542 | 139 | cancellable, error)) |
3543 | 0 | return FALSE; |
3544 | | |
3545 | 139 | if (!glnx_opendirat (self->tmp_dir_fd, _OSTREE_CACHE_DIR, TRUE, &self->cache_dir_fd, error)) |
3546 | 0 | return FALSE; |
3547 | 139 | } |
3548 | | |
3549 | | /* If we weren't created via ostree_sysroot_get_repo(), for backwards |
3550 | | * compatibility we need to figure out now whether or not we refer to the |
3551 | | * system repo. See also ostree-sysroot.c. |
3552 | | */ |
3553 | 139 | if (self->sysroot_kind == OSTREE_REPO_SYSROOT_KIND_UNKNOWN) |
3554 | 139 | { |
3555 | 139 | struct stat system_stbuf; |
3556 | | /* Ignore any errors if we can't access /ostree/repo */ |
3557 | 139 | if (fstatat (AT_FDCWD, "/ostree/repo", &system_stbuf, 0) == 0) |
3558 | 0 | { |
3559 | | /* Are we the same as /ostree/repo? */ |
3560 | 0 | if (self->device == system_stbuf.st_dev && self->inode == system_stbuf.st_ino) |
3561 | 0 | self->sysroot_kind = OSTREE_REPO_SYSROOT_KIND_IS_SYSROOT_OSTREE; |
3562 | 0 | else |
3563 | 0 | self->sysroot_kind = OSTREE_REPO_SYSROOT_KIND_NO; |
3564 | 0 | } |
3565 | 139 | else |
3566 | 139 | self->sysroot_kind = OSTREE_REPO_SYSROOT_KIND_NO; |
3567 | 139 | } |
3568 | | |
3569 | 139 | if (!ostree_repo_reload_config (self, cancellable, error)) |
3570 | 0 | return FALSE; |
3571 | | |
3572 | 139 | self->inited = TRUE; |
3573 | 139 | return TRUE; |
3574 | 139 | } |
3575 | | |
3576 | | /** |
3577 | | * ostree_repo_set_disable_fsync: |
3578 | | * @self: An #OstreeRepo |
3579 | | * @disable_fsync: If %TRUE, do not fsync |
3580 | | * |
3581 | | * Disable requests to fsync() to stable storage during commits. This |
3582 | | * option should only be used by build system tools which are creating |
3583 | | * disposable virtual machines, or have higher level mechanisms for |
3584 | | * ensuring data consistency. |
3585 | | */ |
3586 | | void |
3587 | | ostree_repo_set_disable_fsync (OstreeRepo *self, gboolean disable_fsync) |
3588 | 0 | { |
3589 | 0 | self->disable_fsync = disable_fsync; |
3590 | 0 | } |
3591 | | |
3592 | | /** |
3593 | | * ostree_repo_set_cache_dir: |
3594 | | * @self: An #OstreeRepo |
3595 | | * @dfd: directory fd |
3596 | | * @path: subpath in @dfd |
3597 | | * @cancellable: a #GCancellable |
3598 | | * @error: a #GError |
3599 | | * |
3600 | | * Set a custom location for the cache directory used for e.g. |
3601 | | * per-remote summary caches. Setting this manually is useful when |
3602 | | * doing operations on a system repo as a user because you don't have |
3603 | | * write permissions in the repo, where the cache is normally stored. |
3604 | | * |
3605 | | * Since: 2016.5 |
3606 | | */ |
3607 | | gboolean |
3608 | | ostree_repo_set_cache_dir (OstreeRepo *self, int dfd, const char *path, GCancellable *cancellable, |
3609 | | GError **error) |
3610 | 0 | { |
3611 | 0 | glnx_autofd int fd = -1; |
3612 | 0 | if (!glnx_opendirat (dfd, path, TRUE, &fd, error)) |
3613 | 0 | return FALSE; |
3614 | | |
3615 | 0 | glnx_close_fd (&self->cache_dir_fd); |
3616 | 0 | self->cache_dir_fd = g_steal_fd (&fd); |
3617 | |
|
3618 | 0 | return TRUE; |
3619 | 0 | } |
3620 | | |
3621 | | /** |
3622 | | * ostree_repo_get_disable_fsync: |
3623 | | * @self: An #OstreeRepo |
3624 | | * |
3625 | | * For more information see ostree_repo_set_disable_fsync(). |
3626 | | * |
3627 | | * Returns: Whether or not fsync() is enabled for this repo. |
3628 | | */ |
3629 | | gboolean |
3630 | | ostree_repo_get_disable_fsync (OstreeRepo *self) |
3631 | 0 | { |
3632 | 0 | return self->disable_fsync; |
3633 | 0 | } |
3634 | | |
3635 | | /* Replace the contents of a file, honoring the repository's fsync |
3636 | | * policy. |
3637 | | */ |
3638 | | gboolean |
3639 | | _ostree_repo_file_replace_contents (OstreeRepo *self, int dfd, const char *path, const guint8 *buf, |
3640 | | gsize len, GCancellable *cancellable, GError **error) |
3641 | 0 | { |
3642 | 0 | return glnx_file_replace_contents_at (dfd, path, buf, len, |
3643 | 0 | self->disable_fsync ? GLNX_FILE_REPLACE_NODATASYNC |
3644 | 0 | : GLNX_FILE_REPLACE_DATASYNC_NEW, |
3645 | 0 | cancellable, error); |
3646 | 0 | } |
3647 | | |
3648 | | /** |
3649 | | * ostree_repo_get_path: |
3650 | | * @self: Repo |
3651 | | * |
3652 | | * Note that since the introduction of ostree_repo_open_at(), this function may |
3653 | | * return a process-specific path in `/proc` if the repository was created using |
3654 | | * that API. In general, you should avoid use of this API. |
3655 | | * |
3656 | | * Returns: (transfer none): Path to repo |
3657 | | */ |
3658 | | GFile * |
3659 | | ostree_repo_get_path (OstreeRepo *self) |
3660 | 0 | { |
3661 | | /* Did we have an abspath? Return it */ |
3662 | 0 | if (self->repodir) |
3663 | 0 | return self->repodir; |
3664 | | /* Lazily create a fd-relative path */ |
3665 | 0 | if (!self->repodir_fdrel) |
3666 | 0 | self->repodir_fdrel = ot_fdrel_to_gfile (self->repo_dir_fd, "."); |
3667 | 0 | return self->repodir_fdrel; |
3668 | 0 | } |
3669 | | |
3670 | | /** |
3671 | | * ostree_repo_get_dfd: |
3672 | | * @self: Repo |
3673 | | * |
3674 | | * In some cases it's useful for applications to access the repository |
3675 | | * directly; for example, writing content into `repo/tmp` ensures it's |
3676 | | * on the same filesystem. Another case is detecting the mtime on the |
3677 | | * repository (to see whether a ref was written). |
3678 | | * |
3679 | | * Returns: File descriptor for repository root - owned by @self |
3680 | | * Since: 2016.4 |
3681 | | */ |
3682 | | int |
3683 | | ostree_repo_get_dfd (OstreeRepo *self) |
3684 | 0 | { |
3685 | 0 | g_return_val_if_fail (self->repo_dir_fd != -1, -1); |
3686 | 0 | return self->repo_dir_fd; |
3687 | 0 | } |
3688 | | |
3689 | | /** |
3690 | | * ostree_repo_hash: |
3691 | | * @self: an #OstreeRepo |
3692 | | * |
3693 | | * Calculate a hash value for the given open repository, suitable for use when |
3694 | | * putting it into a hash table. It is an error to call this on an #OstreeRepo |
3695 | | * which is not yet open, as a persistent hash value cannot be calculated until |
3696 | | * the repository is open and the inode of its root directory has been loaded. |
3697 | | * |
3698 | | * This function does no I/O. |
3699 | | * |
3700 | | * Returns: hash value for the #OstreeRepo |
3701 | | * Since: 2017.12 |
3702 | | */ |
3703 | | guint |
3704 | | ostree_repo_hash (OstreeRepo *self) |
3705 | 0 | { |
3706 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), 0); |
3707 | | |
3708 | | /* We cannot hash non-open repositories, since their hash value would change |
3709 | | * once they’re opened, resulting in false lookup misses and the inability to |
3710 | | * remove them from a hash table. */ |
3711 | 0 | g_assert (self->repo_dir_fd >= 0); |
3712 | | |
3713 | | /* device and inode numbers are distributed fairly uniformly, so we can’t |
3714 | | * do much better than just combining them. No need to rehash to even out |
3715 | | * the distribution. */ |
3716 | 0 | return (self->device ^ self->inode); |
3717 | 0 | } |
3718 | | |
3719 | | /** |
3720 | | * ostree_repo_equal: |
3721 | | * @a: an #OstreeRepo |
3722 | | * @b: an #OstreeRepo |
3723 | | * |
3724 | | * Check whether two opened repositories are the same on disk: if their root |
3725 | | * directories are the same inode. If @a or @b are not open yet (due to |
3726 | | * ostree_repo_open() not being called on them yet), %FALSE will be returned. |
3727 | | * |
3728 | | * Returns: %TRUE if @a and @b are the same repository on disk, %FALSE otherwise |
3729 | | * Since: 2017.12 |
3730 | | */ |
3731 | | gboolean |
3732 | | ostree_repo_equal (OstreeRepo *a, OstreeRepo *b) |
3733 | 0 | { |
3734 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (a), FALSE); |
3735 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (b), FALSE); |
3736 | | |
3737 | 0 | if (a->repo_dir_fd < 0 || b->repo_dir_fd < 0) |
3738 | 0 | return FALSE; |
3739 | | |
3740 | 0 | return (a->device == b->device && a->inode == b->inode); |
3741 | 0 | } |
3742 | | |
3743 | | OstreeRepoMode |
3744 | | ostree_repo_get_mode (OstreeRepo *self) |
3745 | 0 | { |
3746 | 0 | g_assert (self != NULL); |
3747 | 0 | g_assert (self->inited); |
3748 | | |
3749 | 0 | return self->mode; |
3750 | 0 | } |
3751 | | |
3752 | | /** |
3753 | | * ostree_repo_get_min_free_space_bytes: |
3754 | | * @self: Repo |
3755 | | * @out_reserved_bytes: (out): Location to store the result |
3756 | | * @error: Return location for a #GError |
3757 | | * |
3758 | | * Determine the number of bytes of free disk space that are reserved according |
3759 | | * to the repo config and return that number in @out_reserved_bytes. See the |
3760 | | * documentation for the core.min-free-space-size and |
3761 | | * core.min-free-space-percent repo config options. |
3762 | | * |
3763 | | * Returns: %TRUE on success, %FALSE otherwise. |
3764 | | * Since: 2018.9 |
3765 | | */ |
3766 | | gboolean |
3767 | | ostree_repo_get_min_free_space_bytes (OstreeRepo *self, guint64 *out_reserved_bytes, GError **error) |
3768 | 139 | { |
3769 | 139 | g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); |
3770 | 139 | g_return_val_if_fail (out_reserved_bytes != NULL, FALSE); |
3771 | 139 | g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
3772 | | |
3773 | 89 | if (!min_free_space_calculate_reserved_bytes (self, out_reserved_bytes, error)) |
3774 | 0 | return glnx_prefix_error (error, "Error calculating min-free-space bytes"); |
3775 | | |
3776 | 89 | return TRUE; |
3777 | 89 | } |
3778 | | |
3779 | | /** |
3780 | | * ostree_repo_get_parent: |
3781 | | * @self: Repo |
3782 | | * |
3783 | | * Before this function can be used, ostree_repo_init() must have been |
3784 | | * called. |
3785 | | * |
3786 | | * Returns: (transfer none) (nullable): Parent repository, or %NULL if none |
3787 | | */ |
3788 | | OstreeRepo * |
3789 | | ostree_repo_get_parent (OstreeRepo *self) |
3790 | 0 | { |
3791 | 0 | return self->parent_repo; |
3792 | 0 | } |
3793 | | |
3794 | | static gboolean |
3795 | | list_loose_objects_at (OstreeRepo *self, GVariant *dummy_value, GHashTable *inout_objects, int dfd, |
3796 | | const char *prefix, const char *commit_starting_with, |
3797 | | GCancellable *cancellable, GError **error) |
3798 | 0 | { |
3799 | 0 | GVariant *key; |
3800 | |
|
3801 | 0 | g_auto (GLnxDirFdIterator) dfd_iter = { |
3802 | 0 | 0, |
3803 | 0 | }; |
3804 | 0 | gboolean exists; |
3805 | 0 | if (!ot_dfd_iter_init_allow_noent (dfd, prefix, &dfd_iter, &exists, error)) |
3806 | 0 | return FALSE; |
3807 | | /* Note early return */ |
3808 | 0 | if (!exists) |
3809 | 0 | return TRUE; |
3810 | | |
3811 | 0 | while (TRUE) |
3812 | 0 | { |
3813 | 0 | struct dirent *dent; |
3814 | |
|
3815 | 0 | if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) |
3816 | 0 | return FALSE; |
3817 | 0 | if (dent == NULL) |
3818 | 0 | break; |
3819 | | |
3820 | 0 | const char *name = dent->d_name; |
3821 | 0 | if (strcmp (name, ".") == 0 || strcmp (name, "..") == 0) |
3822 | 0 | continue; |
3823 | | |
3824 | 0 | const char *dot = strrchr (name, '.'); |
3825 | 0 | if (!dot) |
3826 | 0 | continue; |
3827 | | |
3828 | 0 | OstreeObjectType objtype; |
3829 | 0 | if ((self->mode == OSTREE_REPO_MODE_ARCHIVE && strcmp (dot, ".filez") == 0) |
3830 | 0 | || ((_ostree_repo_mode_is_bare (self->mode)) && strcmp (dot, ".file") == 0)) |
3831 | 0 | objtype = OSTREE_OBJECT_TYPE_FILE; |
3832 | 0 | else if (strcmp (dot, ".dirtree") == 0) |
3833 | 0 | objtype = OSTREE_OBJECT_TYPE_DIR_TREE; |
3834 | 0 | else if (strcmp (dot, ".dirmeta") == 0) |
3835 | 0 | objtype = OSTREE_OBJECT_TYPE_DIR_META; |
3836 | 0 | else if (strcmp (dot, ".commit") == 0) |
3837 | 0 | objtype = OSTREE_OBJECT_TYPE_COMMIT; |
3838 | 0 | else if (strcmp (dot, ".payload-link") == 0) |
3839 | 0 | objtype = OSTREE_OBJECT_TYPE_PAYLOAD_LINK; |
3840 | 0 | else |
3841 | 0 | continue; |
3842 | | |
3843 | 0 | if ((dot - name) != 62) |
3844 | 0 | continue; |
3845 | | |
3846 | 0 | char buf[OSTREE_SHA256_STRING_LEN + 1]; |
3847 | |
|
3848 | 0 | memcpy (buf, prefix, 2); |
3849 | 0 | memcpy (buf + 2, name, 62); |
3850 | 0 | buf[sizeof (buf) - 1] = '\0'; |
3851 | | |
3852 | | /* if we passed in a "starting with" argument, then |
3853 | | we only want to return .commit objects with a checksum |
3854 | | that matches the commit_starting_with argument */ |
3855 | 0 | if (commit_starting_with) |
3856 | 0 | { |
3857 | | /* object is not a commit, do not add to array */ |
3858 | 0 | if (objtype != OSTREE_OBJECT_TYPE_COMMIT) |
3859 | 0 | continue; |
3860 | | |
3861 | | /* commit checksum does not match "starting with", do not add to array */ |
3862 | 0 | if (!g_str_has_prefix (buf, commit_starting_with)) |
3863 | 0 | continue; |
3864 | 0 | } |
3865 | | |
3866 | 0 | key = ostree_object_name_serialize (buf, objtype); |
3867 | | |
3868 | | /* transfer ownership */ |
3869 | 0 | if (dummy_value) |
3870 | 0 | g_hash_table_replace (inout_objects, g_variant_ref_sink (key), g_variant_ref (dummy_value)); |
3871 | 0 | else |
3872 | 0 | g_hash_table_add (inout_objects, g_variant_ref_sink (key)); |
3873 | 0 | } |
3874 | | |
3875 | 0 | return TRUE; |
3876 | 0 | } |
3877 | | |
3878 | | static gboolean |
3879 | | list_loose_objects (OstreeRepo *self, GVariant *dummy_value, GHashTable *inout_objects, |
3880 | | const char *commit_starting_with, GCancellable *cancellable, GError **error) |
3881 | 0 | { |
3882 | 0 | static const gchar hexchars[] = "0123456789abcdef"; |
3883 | |
|
3884 | 0 | for (guint c = 0; c < 256; c++) |
3885 | 0 | { |
3886 | 0 | char buf[3]; |
3887 | 0 | buf[0] = hexchars[c >> 4]; |
3888 | 0 | buf[1] = hexchars[c & 0xF]; |
3889 | 0 | buf[2] = '\0'; |
3890 | 0 | if (!list_loose_objects_at (self, dummy_value, inout_objects, self->objects_dir_fd, buf, |
3891 | 0 | commit_starting_with, cancellable, error)) |
3892 | 0 | return FALSE; |
3893 | 0 | } |
3894 | | |
3895 | 0 | return TRUE; |
3896 | 0 | } |
3897 | | |
3898 | | static gboolean |
3899 | | load_metadata_internal (OstreeRepo *self, OstreeObjectType objtype, const char *sha256, |
3900 | | gboolean error_if_not_found, GVariant **out_variant, |
3901 | | GInputStream **out_stream, guint64 *out_size, |
3902 | | OstreeRepoCommitState *out_state, GCancellable *cancellable, GError **error) |
3903 | 0 | { |
3904 | 0 | char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; |
3905 | 0 | glnx_autofd int fd = -1; |
3906 | 0 | g_autoptr (GInputStream) ret_stream = NULL; |
3907 | 0 | g_autoptr (GVariant) ret_variant = NULL; |
3908 | |
|
3909 | 0 | g_return_val_if_fail (OSTREE_OBJECT_TYPE_IS_META (objtype), FALSE); |
3910 | 0 | g_return_val_if_fail (objtype == OSTREE_OBJECT_TYPE_COMMIT || out_state == NULL, FALSE); |
3911 | | |
3912 | | /* Ensure this is set to NULL if we didn't find the object */ |
3913 | 0 | if (out_variant) |
3914 | 0 | *out_variant = NULL; |
3915 | | |
3916 | | /* Special caching for dirmeta objects, since they're commonly referenced many |
3917 | | * times. |
3918 | | */ |
3919 | 0 | const gboolean is_dirmeta_cachable |
3920 | 0 | = (objtype == OSTREE_OBJECT_TYPE_DIR_META && out_variant && !out_stream); |
3921 | 0 | if (is_dirmeta_cachable) |
3922 | 0 | { |
3923 | 0 | GMutex *lock = &self->cache_lock; |
3924 | 0 | g_mutex_lock (lock); |
3925 | 0 | GVariant *cache_hit = NULL; |
3926 | | /* Look it up, if we have a cache */ |
3927 | 0 | if (self->dirmeta_cache) |
3928 | 0 | cache_hit = g_hash_table_lookup (self->dirmeta_cache, sha256); |
3929 | 0 | if (cache_hit) |
3930 | 0 | *out_variant = g_variant_ref (cache_hit); |
3931 | 0 | g_mutex_unlock (lock); |
3932 | 0 | if (cache_hit) |
3933 | 0 | return TRUE; |
3934 | 0 | } |
3935 | | |
3936 | 0 | _ostree_loose_path (loose_path_buf, sha256, objtype, self->mode); |
3937 | |
|
3938 | 0 | if (!ot_openat_ignore_enoent (self->objects_dir_fd, loose_path_buf, &fd, error)) |
3939 | 0 | return FALSE; |
3940 | | |
3941 | 0 | if (fd < 0 && self->commit_stagedir.initialized) |
3942 | 0 | { |
3943 | 0 | if (!ot_openat_ignore_enoent (self->commit_stagedir.fd, loose_path_buf, &fd, error)) |
3944 | 0 | return FALSE; |
3945 | 0 | } |
3946 | | |
3947 | 0 | if (fd != -1) |
3948 | 0 | { |
3949 | 0 | struct stat stbuf; |
3950 | 0 | if (!glnx_fstat (fd, &stbuf, error)) |
3951 | 0 | return FALSE; |
3952 | 0 | if (out_variant) |
3953 | 0 | { |
3954 | 0 | if (!ot_variant_read_fd (fd, 0, ostree_metadata_variant_type (objtype), TRUE, |
3955 | 0 | &ret_variant, error)) |
3956 | 0 | return FALSE; |
3957 | | |
3958 | | /* Now, let's put it in the cache */ |
3959 | 0 | if (is_dirmeta_cachable) |
3960 | 0 | { |
3961 | 0 | GMutex *lock = &self->cache_lock; |
3962 | 0 | g_mutex_lock (lock); |
3963 | 0 | if (self->dirmeta_cache) |
3964 | 0 | g_hash_table_replace (self->dirmeta_cache, g_strdup (sha256), |
3965 | 0 | g_variant_ref (ret_variant)); |
3966 | 0 | g_mutex_unlock (lock); |
3967 | 0 | } |
3968 | 0 | } |
3969 | 0 | else if (out_stream) |
3970 | 0 | { |
3971 | 0 | ret_stream = g_unix_input_stream_new (fd, TRUE); |
3972 | 0 | if (!ret_stream) |
3973 | 0 | return FALSE; |
3974 | 0 | fd = -1; /* Transfer ownership */ |
3975 | 0 | } |
3976 | | |
3977 | 0 | if (out_size) |
3978 | 0 | *out_size = stbuf.st_size; |
3979 | |
|
3980 | 0 | if (out_state) |
3981 | 0 | { |
3982 | 0 | g_autofree char *commitpartial_path = _ostree_get_commitpartial_path (sha256); |
3983 | 0 | *out_state = 0; |
3984 | |
|
3985 | 0 | glnx_autofd int commitpartial_fd = -1; |
3986 | 0 | if (!ot_openat_ignore_enoent (self->repo_dir_fd, commitpartial_path, &commitpartial_fd, |
3987 | 0 | error)) |
3988 | 0 | return FALSE; |
3989 | 0 | if (commitpartial_fd != -1) |
3990 | 0 | { |
3991 | 0 | *out_state |= OSTREE_REPO_COMMIT_STATE_PARTIAL; |
3992 | 0 | char reason; |
3993 | 0 | if (read (commitpartial_fd, &reason, 1) == 1) |
3994 | 0 | { |
3995 | 0 | if (reason == 'f') |
3996 | 0 | *out_state |= OSTREE_REPO_COMMIT_STATE_FSCK_PARTIAL; |
3997 | 0 | } |
3998 | 0 | } |
3999 | 0 | } |
4000 | 0 | } |
4001 | 0 | else if (self->parent_repo) |
4002 | 0 | { |
4003 | | /* Directly recurse to simplify out parameters */ |
4004 | 0 | return load_metadata_internal (self->parent_repo, objtype, sha256, error_if_not_found, |
4005 | 0 | out_variant, out_stream, out_size, out_state, cancellable, |
4006 | 0 | error); |
4007 | 0 | } |
4008 | 0 | else if (error_if_not_found) |
4009 | 0 | { |
4010 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "No such metadata object %s.%s", sha256, |
4011 | 0 | ostree_object_type_to_string (objtype)); |
4012 | 0 | return FALSE; |
4013 | 0 | } |
4014 | | |
4015 | 0 | ot_transfer_out_value (out_variant, &ret_variant); |
4016 | 0 | ot_transfer_out_value (out_stream, &ret_stream); |
4017 | 0 | return TRUE; |
4018 | 0 | } |
4019 | | |
4020 | | static GVariant * |
4021 | | filemeta_to_stat (struct stat *stbuf, GVariant *metadata) |
4022 | 0 | { |
4023 | 0 | guint32 uid, gid, mode; |
4024 | 0 | GVariant *xattrs; |
4025 | |
|
4026 | 0 | g_variant_get (metadata, "(uuu@a(ayay))", &uid, &gid, &mode, &xattrs); |
4027 | 0 | stbuf->st_uid = GUINT32_FROM_BE (uid); |
4028 | 0 | stbuf->st_gid = GUINT32_FROM_BE (gid); |
4029 | 0 | stbuf->st_mode = GUINT32_FROM_BE (mode); |
4030 | |
|
4031 | 0 | return xattrs; |
4032 | 0 | } |
4033 | | |
4034 | | static gboolean |
4035 | | repo_load_file_archive (OstreeRepo *self, const char *checksum, GInputStream **out_input, |
4036 | | GFileInfo **out_file_info, GVariant **out_xattrs, GCancellable *cancellable, |
4037 | | GError **error) |
4038 | 0 | { |
4039 | 0 | struct stat stbuf; |
4040 | 0 | char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; |
4041 | 0 | _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, self->mode); |
4042 | |
|
4043 | 0 | glnx_autofd int fd = -1; |
4044 | 0 | if (!ot_openat_ignore_enoent (self->objects_dir_fd, loose_path_buf, &fd, error)) |
4045 | 0 | return FALSE; |
4046 | | |
4047 | 0 | if (fd < 0 && self->commit_stagedir.initialized) |
4048 | 0 | { |
4049 | 0 | if (!ot_openat_ignore_enoent (self->commit_stagedir.fd, loose_path_buf, &fd, error)) |
4050 | 0 | return FALSE; |
4051 | 0 | } |
4052 | | |
4053 | 0 | if (fd != -1) |
4054 | 0 | { |
4055 | 0 | if (!glnx_fstat (fd, &stbuf, error)) |
4056 | 0 | return FALSE; |
4057 | | |
4058 | 0 | g_autoptr (GInputStream) tmp_stream = g_unix_input_stream_new (g_steal_fd (&fd), TRUE); |
4059 | | /* Note return here */ |
4060 | 0 | return ostree_content_stream_parse (TRUE, tmp_stream, stbuf.st_size, TRUE, out_input, |
4061 | 0 | out_file_info, out_xattrs, cancellable, error); |
4062 | 0 | } |
4063 | 0 | else if (self->parent_repo) |
4064 | 0 | { |
4065 | 0 | return ostree_repo_load_file (self->parent_repo, checksum, out_input, out_file_info, |
4066 | 0 | out_xattrs, cancellable, error); |
4067 | 0 | } |
4068 | 0 | else |
4069 | 0 | { |
4070 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Couldn't find file object '%s'", |
4071 | 0 | checksum); |
4072 | 0 | return FALSE; |
4073 | 0 | } |
4074 | 0 | } |
4075 | | |
4076 | | static GVariant * |
4077 | | _ostree_repo_read_xattrs_file_link (OstreeRepo *self, const char *checksum, |
4078 | | GCancellable *cancellable, GError **error) |
4079 | 0 | { |
4080 | 0 | g_assert (self != NULL); |
4081 | 0 | g_assert (checksum != NULL); |
4082 | | |
4083 | 0 | char xattr_path[_OSTREE_LOOSE_PATH_MAX]; |
4084 | 0 | _ostree_loose_path (xattr_path, checksum, OSTREE_OBJECT_TYPE_FILE_XATTRS_LINK, self->mode); |
4085 | |
|
4086 | 0 | g_autoptr (GVariant) xattrs = NULL; |
4087 | 0 | glnx_autofd int fd = -1; |
4088 | 0 | if (!glnx_openat_rdonly (self->objects_dir_fd, xattr_path, FALSE, &fd, error)) |
4089 | 0 | return FALSE; |
4090 | | |
4091 | 0 | g_assert (fd >= 0); |
4092 | 0 | if (!ot_variant_read_fd (fd, 0, G_VARIANT_TYPE ("a(ayay)"), TRUE, &xattrs, error)) |
4093 | 0 | return glnx_prefix_error_null (error, "Deserializing xattrs content"); |
4094 | | |
4095 | 0 | g_assert (xattrs != NULL); |
4096 | 0 | return g_steal_pointer (&xattrs); |
4097 | 0 | } |
4098 | | |
4099 | | gboolean |
4100 | | _ostree_repo_load_file_bare (OstreeRepo *self, const char *checksum, int *out_fd, |
4101 | | struct stat *out_stbuf, char **out_symlink, GVariant **out_xattrs, |
4102 | | GCancellable *cancellable, GError **error) |
4103 | 0 | { |
4104 | | /* The bottom case recursing on the parent repo */ |
4105 | 0 | if (self == NULL) |
4106 | 0 | { |
4107 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Couldn't find file object '%s'", |
4108 | 0 | checksum); |
4109 | 0 | return FALSE; |
4110 | 0 | } |
4111 | | |
4112 | 0 | const char *errprefix = glnx_strjoina ("Opening content object ", checksum); |
4113 | 0 | GLNX_AUTO_PREFIX_ERROR (errprefix, error); |
4114 | |
|
4115 | 0 | struct stat stbuf; |
4116 | 0 | glnx_autofd int fd = -1; |
4117 | 0 | g_autofree char *ret_symlink = NULL; |
4118 | 0 | g_autoptr (GVariant) ret_xattrs = NULL; |
4119 | 0 | char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; |
4120 | 0 | _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, self->mode); |
4121 | | |
4122 | | /* Do a fstatat() and find the object directory that contains this object */ |
4123 | 0 | int objdir_fd = self->objects_dir_fd; |
4124 | 0 | int res; |
4125 | 0 | if ((res = TEMP_FAILURE_RETRY (fstatat (objdir_fd, loose_path_buf, &stbuf, AT_SYMLINK_NOFOLLOW))) |
4126 | 0 | < 0 |
4127 | 0 | && errno == ENOENT && self->commit_stagedir.initialized) |
4128 | 0 | { |
4129 | 0 | objdir_fd = self->commit_stagedir.fd; |
4130 | 0 | res = TEMP_FAILURE_RETRY (fstatat (objdir_fd, loose_path_buf, &stbuf, AT_SYMLINK_NOFOLLOW)); |
4131 | 0 | } |
4132 | 0 | if (res < 0 && errno != ENOENT) |
4133 | 0 | return glnx_throw_errno_prefix (error, "fstat"); |
4134 | 0 | else if (res < 0) |
4135 | 0 | { |
4136 | 0 | g_assert (errno == ENOENT); |
4137 | 0 | return _ostree_repo_load_file_bare (self->parent_repo, checksum, out_fd, out_stbuf, |
4138 | 0 | out_symlink, out_xattrs, cancellable, error); |
4139 | 0 | } |
4140 | | |
4141 | 0 | const gboolean need_open = (out_fd || (out_xattrs && self->mode == OSTREE_REPO_MODE_BARE) |
4142 | 0 | || self->mode == OSTREE_REPO_MODE_BARE_USER); |
4143 | | /* If it's a regular file and we're requested to return the fd, do it now. As |
4144 | | * a special case in bare-user, we always do an open, since the stat() metadata |
4145 | | * lives there. |
4146 | | */ |
4147 | 0 | if (need_open && S_ISREG (stbuf.st_mode)) |
4148 | 0 | { |
4149 | 0 | fd = openat (objdir_fd, loose_path_buf, O_CLOEXEC | O_RDONLY); |
4150 | 0 | if (fd < 0) |
4151 | 0 | return glnx_throw_errno_prefix (error, "openat"); |
4152 | 0 | } |
4153 | | |
4154 | 0 | if (!(S_ISREG (stbuf.st_mode) || S_ISLNK (stbuf.st_mode))) |
4155 | 0 | return glnx_throw (error, "Not a regular file or symlink"); |
4156 | | |
4157 | | /* In the non-bare-user case, gather symlink info if requested */ |
4158 | 0 | if (self->mode != OSTREE_REPO_MODE_BARE_USER && S_ISLNK (stbuf.st_mode) && out_symlink) |
4159 | 0 | { |
4160 | 0 | ret_symlink = glnx_readlinkat_malloc (objdir_fd, loose_path_buf, cancellable, error); |
4161 | 0 | if (!ret_symlink) |
4162 | 0 | return FALSE; |
4163 | 0 | } |
4164 | | |
4165 | 0 | if (self->mode == OSTREE_REPO_MODE_BARE_USER) |
4166 | 0 | { |
4167 | 0 | g_autoptr (GBytes) bytes = glnx_fgetxattr_bytes (fd, "user.ostreemeta", error); |
4168 | 0 | if (bytes == NULL) |
4169 | 0 | return FALSE; |
4170 | | |
4171 | 0 | g_autoptr (GVariant) metadata = g_variant_ref_sink ( |
4172 | 0 | g_variant_new_from_bytes (OSTREE_FILEMETA_GVARIANT_FORMAT, bytes, FALSE)); |
4173 | 0 | g_autoptr (GVariant) read_xattrs = filemeta_to_stat (&stbuf, metadata); |
4174 | | // Old versions of ostree may have written these xattrs in non-canonical form. |
4175 | | // In order to aid comparisons, let's canonicalize here. |
4176 | 0 | ret_xattrs = _ostree_canonicalize_xattrs (read_xattrs); |
4177 | 0 | if (S_ISLNK (stbuf.st_mode)) |
4178 | 0 | { |
4179 | 0 | if (out_symlink) |
4180 | 0 | { |
4181 | 0 | char targetbuf[PATH_MAX + 1]; |
4182 | 0 | gsize target_size; |
4183 | 0 | g_autoptr (GInputStream) target_input = g_unix_input_stream_new (fd, FALSE); |
4184 | 0 | if (!g_input_stream_read_all (target_input, targetbuf, sizeof (targetbuf), |
4185 | 0 | &target_size, cancellable, error)) |
4186 | 0 | return FALSE; |
4187 | | |
4188 | 0 | ret_symlink = g_strndup (targetbuf, target_size); |
4189 | 0 | } |
4190 | | /* In the symlink case, we don't want to return the bare-user fd */ |
4191 | 0 | glnx_close_fd (&fd); |
4192 | 0 | } |
4193 | 0 | } |
4194 | 0 | else if (self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) |
4195 | 0 | { |
4196 | | /* Canonical info is: uid/gid is 0 and no xattrs, which |
4197 | | might be wrong and thus not validate correctly, but |
4198 | | at least we report something consistent. */ |
4199 | 0 | stbuf.st_uid = stbuf.st_gid = 0; |
4200 | |
|
4201 | 0 | if (out_xattrs) |
4202 | 0 | { |
4203 | 0 | GVariantBuilder builder; |
4204 | 0 | g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)")); |
4205 | 0 | ret_xattrs = g_variant_ref_sink (g_variant_builder_end (&builder)); |
4206 | 0 | } |
4207 | 0 | } |
4208 | 0 | else if (self->mode == OSTREE_REPO_MODE_BARE) |
4209 | 0 | { |
4210 | 0 | if (S_ISREG (stbuf.st_mode) && out_xattrs) |
4211 | 0 | { |
4212 | 0 | if (self->disable_xattrs) |
4213 | 0 | ret_xattrs |
4214 | 0 | = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0)); |
4215 | 0 | else |
4216 | 0 | { |
4217 | 0 | ret_xattrs = ostree_fs_get_all_xattrs (fd, cancellable, error); |
4218 | 0 | if (!ret_xattrs) |
4219 | 0 | return FALSE; |
4220 | 0 | } |
4221 | 0 | } |
4222 | 0 | else if (S_ISLNK (stbuf.st_mode) && out_xattrs) |
4223 | 0 | { |
4224 | 0 | if (self->disable_xattrs) |
4225 | 0 | ret_xattrs |
4226 | 0 | = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0)); |
4227 | 0 | else if (!glnx_dfd_name_get_all_xattrs (objdir_fd, loose_path_buf, &ret_xattrs, |
4228 | 0 | cancellable, error)) |
4229 | 0 | return FALSE; |
4230 | 0 | } |
4231 | 0 | } |
4232 | 0 | else if (self->mode == OSTREE_REPO_MODE_BARE_SPLIT_XATTRS) |
4233 | 0 | { |
4234 | 0 | if (out_xattrs) |
4235 | 0 | { |
4236 | 0 | ret_xattrs = _ostree_repo_read_xattrs_file_link (self, checksum, cancellable, error); |
4237 | 0 | if (ret_xattrs == NULL) |
4238 | 0 | return FALSE; |
4239 | 0 | } |
4240 | 0 | } |
4241 | 0 | else |
4242 | 0 | { |
4243 | 0 | g_assert_not_reached (); |
4244 | 0 | } |
4245 | | |
4246 | 0 | if (out_fd) |
4247 | 0 | *out_fd = g_steal_fd (&fd); |
4248 | 0 | if (out_stbuf) |
4249 | 0 | *out_stbuf = stbuf; |
4250 | 0 | ot_transfer_out_value (out_symlink, &ret_symlink); |
4251 | 0 | ot_transfer_out_value (out_xattrs, &ret_xattrs); |
4252 | 0 | return TRUE; |
4253 | 0 | } |
4254 | | |
4255 | | /** |
4256 | | * ostree_repo_load_file: |
4257 | | * @self: Repo |
4258 | | * @checksum: ASCII SHA256 checksum |
4259 | | * @out_input: (out) (optional) (nullable): File content |
4260 | | * @out_file_info: (out) (optional) (nullable): File information |
4261 | | * @out_xattrs: (out) (optional) (nullable): Extended attributes |
4262 | | * @cancellable: Cancellable |
4263 | | * @error: Error |
4264 | | * |
4265 | | * Load content object, decomposing it into three parts: the actual |
4266 | | * content (for regular files), the metadata, and extended attributes. |
4267 | | */ |
4268 | | gboolean |
4269 | | ostree_repo_load_file (OstreeRepo *self, const char *checksum, GInputStream **out_input, |
4270 | | GFileInfo **out_file_info, GVariant **out_xattrs, GCancellable *cancellable, |
4271 | | GError **error) |
4272 | 0 | { |
4273 | 0 | if (self->mode == OSTREE_REPO_MODE_ARCHIVE) |
4274 | 0 | return repo_load_file_archive (self, checksum, out_input, out_file_info, out_xattrs, |
4275 | 0 | cancellable, error); |
4276 | 0 | else |
4277 | 0 | { |
4278 | 0 | glnx_autofd int fd = -1; |
4279 | 0 | struct stat stbuf; |
4280 | 0 | g_autofree char *symlink_target = NULL; |
4281 | 0 | g_autoptr (GVariant) ret_xattrs = NULL; |
4282 | 0 | if (!_ostree_repo_load_file_bare (self, checksum, out_input ? &fd : NULL, |
4283 | 0 | out_file_info ? &stbuf : NULL, |
4284 | 0 | out_file_info ? &symlink_target : NULL, |
4285 | 0 | out_xattrs ? &ret_xattrs : NULL, cancellable, error)) |
4286 | 0 | return FALSE; |
4287 | | |
4288 | | /* Convert fd → GInputStream and struct stat → GFileInfo */ |
4289 | 0 | if (out_input) |
4290 | 0 | { |
4291 | 0 | if (fd != -1) |
4292 | 0 | *out_input = g_unix_input_stream_new (g_steal_fd (&fd), TRUE); |
4293 | 0 | else |
4294 | 0 | *out_input = NULL; |
4295 | 0 | } |
4296 | 0 | if (out_file_info) |
4297 | 0 | { |
4298 | 0 | *out_file_info = _ostree_stbuf_to_gfileinfo (&stbuf); |
4299 | 0 | if (S_ISLNK (stbuf.st_mode)) |
4300 | 0 | g_file_info_set_symlink_target (*out_file_info, symlink_target); |
4301 | 0 | else |
4302 | 0 | g_assert (S_ISREG (stbuf.st_mode)); |
4303 | 0 | } |
4304 | | |
4305 | 0 | ot_transfer_out_value (out_xattrs, &ret_xattrs); |
4306 | 0 | return TRUE; |
4307 | 0 | } |
4308 | 0 | } |
4309 | | |
4310 | | /** |
4311 | | * ostree_repo_load_object_stream: |
4312 | | * @self: Repo |
4313 | | * @objtype: Object type |
4314 | | * @checksum: ASCII SHA256 checksum |
4315 | | * @out_input: (out): Stream for object |
4316 | | * @out_size: (out): Length of @out_input |
4317 | | * @cancellable: Cancellable |
4318 | | * @error: Error |
4319 | | * |
4320 | | * Load object as a stream; useful when copying objects between |
4321 | | * repositories. |
4322 | | */ |
4323 | | gboolean |
4324 | | ostree_repo_load_object_stream (OstreeRepo *self, OstreeObjectType objtype, const char *checksum, |
4325 | | GInputStream **out_input, guint64 *out_size, |
4326 | | GCancellable *cancellable, GError **error) |
4327 | 0 | { |
4328 | 0 | guint64 size; |
4329 | 0 | g_autoptr (GInputStream) ret_input = NULL; |
4330 | |
|
4331 | 0 | if (OSTREE_OBJECT_TYPE_IS_META (objtype)) |
4332 | 0 | { |
4333 | 0 | if (!load_metadata_internal (self, objtype, checksum, TRUE, NULL, &ret_input, &size, NULL, |
4334 | 0 | cancellable, error)) |
4335 | 0 | return FALSE; |
4336 | 0 | } |
4337 | 0 | else |
4338 | 0 | { |
4339 | 0 | g_autoptr (GInputStream) input = NULL; |
4340 | 0 | g_autoptr (GFileInfo) finfo = NULL; |
4341 | 0 | g_autoptr (GVariant) xattrs = NULL; |
4342 | |
|
4343 | 0 | if (!ostree_repo_load_file (self, checksum, &input, &finfo, &xattrs, cancellable, error)) |
4344 | 0 | return FALSE; |
4345 | | |
4346 | 0 | if (!ostree_raw_file_to_content_stream (input, finfo, xattrs, &ret_input, &size, cancellable, |
4347 | 0 | error)) |
4348 | 0 | return FALSE; |
4349 | 0 | } |
4350 | | |
4351 | 0 | ot_transfer_out_value (out_input, &ret_input); |
4352 | 0 | *out_size = size; |
4353 | 0 | return TRUE; |
4354 | 0 | } |
4355 | | |
4356 | | /* |
4357 | | * _ostree_repo_has_loose_object: |
4358 | | * @loose_path_buf: Buffer of size _OSTREE_LOOSE_PATH_MAX |
4359 | | * |
4360 | | * Locate object in repository; if it exists, @out_is_stored will be |
4361 | | * set to TRUE. @loose_path_buf is always set to the loose path. |
4362 | | */ |
4363 | | gboolean |
4364 | | _ostree_repo_has_loose_object (OstreeRepo *self, const char *checksum, OstreeObjectType objtype, |
4365 | | gboolean *out_is_stored, GCancellable *cancellable, GError **error) |
4366 | 0 | { |
4367 | 0 | char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; |
4368 | 0 | _ostree_loose_path (loose_path_buf, checksum, objtype, self->mode); |
4369 | |
|
4370 | 0 | gboolean found = FALSE; |
4371 | | /* It's easier to share code if we make this an array */ |
4372 | 0 | int dfd_searches[] = { -1, self->objects_dir_fd }; |
4373 | 0 | if (self->commit_stagedir.initialized) |
4374 | 0 | dfd_searches[0] = self->commit_stagedir.fd; |
4375 | 0 | for (guint i = 0; i < G_N_ELEMENTS (dfd_searches); i++) |
4376 | 0 | { |
4377 | 0 | int dfd = dfd_searches[i]; |
4378 | 0 | if (dfd == -1) |
4379 | 0 | continue; |
4380 | 0 | struct stat stbuf; |
4381 | 0 | if (TEMP_FAILURE_RETRY (fstatat (dfd, loose_path_buf, &stbuf, AT_SYMLINK_NOFOLLOW)) < 0) |
4382 | 0 | { |
4383 | 0 | if (errno == ENOENT) |
4384 | 0 | ; /* Next dfd */ |
4385 | 0 | else |
4386 | 0 | return glnx_throw_errno_prefix (error, "fstatat(%s)", loose_path_buf); |
4387 | 0 | } |
4388 | 0 | else |
4389 | 0 | { |
4390 | 0 | found = TRUE; |
4391 | 0 | break; |
4392 | 0 | } |
4393 | 0 | } |
4394 | | |
4395 | 0 | *out_is_stored = found; |
4396 | 0 | return TRUE; |
4397 | 0 | } |
4398 | | |
4399 | | /** |
4400 | | * ostree_repo_has_object: |
4401 | | * @self: Repo |
4402 | | * @objtype: Object type |
4403 | | * @checksum: ASCII SHA256 checksum |
4404 | | * @out_have_object: (out): %TRUE if repository contains object |
4405 | | * @cancellable: Cancellable |
4406 | | * @error: Error |
4407 | | * |
4408 | | * Set @out_have_object to %TRUE if @self contains the given object; |
4409 | | * %FALSE otherwise. |
4410 | | * |
4411 | | * Returns: %FALSE if an unexpected error occurred, %TRUE otherwise |
4412 | | */ |
4413 | | gboolean |
4414 | | ostree_repo_has_object (OstreeRepo *self, OstreeObjectType objtype, const char *checksum, |
4415 | | gboolean *out_have_object, GCancellable *cancellable, GError **error) |
4416 | 0 | { |
4417 | 0 | gboolean ret_have_object = FALSE; |
4418 | |
|
4419 | 0 | if (!_ostree_repo_has_loose_object (self, checksum, objtype, &ret_have_object, cancellable, |
4420 | 0 | error)) |
4421 | 0 | return FALSE; |
4422 | | |
4423 | | /* In the future, here is where we would also look up in metadata pack files */ |
4424 | | |
4425 | 0 | if (!ret_have_object && self->parent_repo) |
4426 | 0 | { |
4427 | 0 | if (!ostree_repo_has_object (self->parent_repo, objtype, checksum, &ret_have_object, |
4428 | 0 | cancellable, error)) |
4429 | 0 | return FALSE; |
4430 | 0 | } |
4431 | | |
4432 | 0 | if (out_have_object) |
4433 | 0 | *out_have_object = ret_have_object; |
4434 | 0 | return TRUE; |
4435 | 0 | } |
4436 | | |
4437 | | /** |
4438 | | * ostree_repo_delete_object: |
4439 | | * @self: Repo |
4440 | | * @objtype: Object type |
4441 | | * @sha256: Checksum |
4442 | | * @cancellable: Cancellable |
4443 | | * @error: Error |
4444 | | * |
4445 | | * Remove the object of type @objtype with checksum @sha256 |
4446 | | * from the repository. An error of type %G_IO_ERROR_NOT_FOUND |
4447 | | * is thrown if the object does not exist. |
4448 | | */ |
4449 | | gboolean |
4450 | | ostree_repo_delete_object (OstreeRepo *self, OstreeObjectType objtype, const char *sha256, |
4451 | | GCancellable *cancellable, GError **error) |
4452 | 0 | { |
4453 | 0 | char loose_path[_OSTREE_LOOSE_PATH_MAX]; |
4454 | 0 | _ostree_loose_path (loose_path, sha256, objtype, self->mode); |
4455 | |
|
4456 | 0 | if (objtype == OSTREE_OBJECT_TYPE_COMMIT) |
4457 | 0 | { |
4458 | 0 | char meta_loose[_OSTREE_LOOSE_PATH_MAX]; |
4459 | |
|
4460 | 0 | _ostree_loose_path (meta_loose, sha256, OSTREE_OBJECT_TYPE_COMMIT_META, self->mode); |
4461 | |
|
4462 | 0 | if (!ot_ensure_unlinked_at (self->objects_dir_fd, meta_loose, error)) |
4463 | 0 | return FALSE; |
4464 | 0 | } |
4465 | | |
4466 | 0 | if (!glnx_unlinkat (self->objects_dir_fd, loose_path, 0, error)) |
4467 | 0 | return glnx_prefix_error (error, "Deleting object %s.%s", sha256, |
4468 | 0 | ostree_object_type_to_string (objtype)); |
4469 | | |
4470 | | /* If the repository is configured to use tombstone commits, create one when deleting a commit. |
4471 | | */ |
4472 | 0 | if (objtype == OSTREE_OBJECT_TYPE_COMMIT) |
4473 | 0 | { |
4474 | 0 | gboolean tombstone_commits = FALSE; |
4475 | 0 | GKeyFile *readonly_config = ostree_repo_get_config (self); |
4476 | 0 | if (!ot_keyfile_get_boolean_with_default (readonly_config, "core", "tombstone-commits", FALSE, |
4477 | 0 | &tombstone_commits, error)) |
4478 | 0 | return FALSE; |
4479 | | |
4480 | 0 | if (tombstone_commits) |
4481 | 0 | { |
4482 | 0 | g_auto (GVariantBuilder) builder = OT_VARIANT_BUILDER_INITIALIZER; |
4483 | 0 | g_autoptr (GVariant) variant = NULL; |
4484 | |
|
4485 | 0 | g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); |
4486 | 0 | g_variant_builder_add (&builder, "{sv}", "commit", g_variant_new_bytestring (sha256)); |
4487 | 0 | variant = g_variant_ref_sink (g_variant_builder_end (&builder)); |
4488 | 0 | if (!ostree_repo_write_metadata_trusted (self, OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT, |
4489 | 0 | sha256, variant, cancellable, error)) |
4490 | 0 | return FALSE; |
4491 | 0 | } |
4492 | 0 | } |
4493 | | |
4494 | 0 | return TRUE; |
4495 | 0 | } |
4496 | | |
4497 | | /* Thin wrapper for _ostree_verify_metadata_object() */ |
4498 | | static gboolean |
4499 | | fsck_metadata_object (OstreeRepo *self, OstreeObjectType objtype, const char *sha256, |
4500 | | GCancellable *cancellable, GError **error) |
4501 | 0 | { |
4502 | 0 | const char *errmsg = glnx_strjoina ("fsck ", sha256, ".", ostree_object_type_to_string (objtype)); |
4503 | 0 | GLNX_AUTO_PREFIX_ERROR (errmsg, error); |
4504 | 0 | g_autoptr (GVariant) metadata = NULL; |
4505 | 0 | if (!load_metadata_internal (self, objtype, sha256, TRUE, &metadata, NULL, NULL, NULL, |
4506 | 0 | cancellable, error)) |
4507 | 0 | return FALSE; |
4508 | | |
4509 | 0 | return _ostree_verify_metadata_object (objtype, sha256, metadata, error); |
4510 | 0 | } |
4511 | | |
4512 | | static gboolean |
4513 | | fsck_content_object (OstreeRepo *self, const char *sha256, GCancellable *cancellable, |
4514 | | GError **error) |
4515 | 0 | { |
4516 | 0 | const char *errmsg = glnx_strjoina ("fsck content object ", sha256); |
4517 | 0 | GLNX_AUTO_PREFIX_ERROR (errmsg, error); |
4518 | 0 | g_autoptr (GInputStream) input = NULL; |
4519 | 0 | g_autoptr (GFileInfo) file_info = NULL; |
4520 | 0 | g_autoptr (GVariant) xattrs = NULL; |
4521 | |
|
4522 | 0 | if (!ostree_repo_load_file (self, sha256, &input, &file_info, &xattrs, cancellable, error)) |
4523 | 0 | return FALSE; |
4524 | | |
4525 | | /* TODO more consistency checks here */ |
4526 | 0 | const guint32 mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); |
4527 | 0 | if (!ostree_validate_structureof_file_mode (mode, error)) |
4528 | 0 | return FALSE; |
4529 | | |
4530 | 0 | g_autofree guchar *computed_csum = NULL; |
4531 | 0 | if (!ostree_checksum_file_from_input (file_info, xattrs, input, OSTREE_OBJECT_TYPE_FILE, |
4532 | 0 | &computed_csum, cancellable, error)) |
4533 | 0 | return FALSE; |
4534 | | |
4535 | 0 | char actual_checksum[OSTREE_SHA256_STRING_LEN + 1]; |
4536 | 0 | ostree_checksum_inplace_from_bytes (computed_csum, actual_checksum); |
4537 | 0 | return _ostree_compare_object_checksum (OSTREE_OBJECT_TYPE_FILE, sha256, actual_checksum, error); |
4538 | 0 | } |
4539 | | |
4540 | | /** |
4541 | | * ostree_repo_fsck_object: |
4542 | | * @self: Repo |
4543 | | * @objtype: Object type |
4544 | | * @sha256: Checksum |
4545 | | * @cancellable: Cancellable |
4546 | | * @error: Error |
4547 | | * |
4548 | | * Verify consistency of the object; this performs checks only relevant to the |
4549 | | * immediate object itself, such as checksumming. This API call will not itself |
4550 | | * traverse metadata objects for example. |
4551 | | * |
4552 | | * Since: 2017.15 |
4553 | | */ |
4554 | | gboolean |
4555 | | ostree_repo_fsck_object (OstreeRepo *self, OstreeObjectType objtype, const char *sha256, |
4556 | | GCancellable *cancellable, GError **error) |
4557 | 0 | { |
4558 | 0 | if (OSTREE_OBJECT_TYPE_IS_META (objtype)) |
4559 | 0 | return fsck_metadata_object (self, objtype, sha256, cancellable, error); |
4560 | 0 | else |
4561 | 0 | return fsck_content_object (self, sha256, cancellable, error); |
4562 | 0 | } |
4563 | | |
4564 | | /** |
4565 | | * ostree_repo_import_object_from: |
4566 | | * @self: Destination repo |
4567 | | * @source: Source repo |
4568 | | * @objtype: Object type |
4569 | | * @checksum: checksum |
4570 | | * @cancellable: Cancellable |
4571 | | * @error: Error |
4572 | | * |
4573 | | * Copy object named by @objtype and @checksum into @self from the |
4574 | | * source repository @source. If both repositories are of the same |
4575 | | * type and on the same filesystem, this will simply be a fast Unix |
4576 | | * hard link operation. |
4577 | | * |
4578 | | * Otherwise, a copy will be performed. |
4579 | | */ |
4580 | | gboolean |
4581 | | ostree_repo_import_object_from (OstreeRepo *self, OstreeRepo *source, OstreeObjectType objtype, |
4582 | | const char *checksum, GCancellable *cancellable, GError **error) |
4583 | 0 | { |
4584 | 0 | return ostree_repo_import_object_from_with_trust (self, source, objtype, checksum, TRUE, |
4585 | 0 | cancellable, error); |
4586 | 0 | } |
4587 | | |
4588 | | /** |
4589 | | * ostree_repo_import_object_from_with_trust: |
4590 | | * @self: Destination repo |
4591 | | * @source: Source repo |
4592 | | * @objtype: Object type |
4593 | | * @checksum: checksum |
4594 | | * @trusted: If %TRUE, assume the source repo is valid and trusted |
4595 | | * @cancellable: Cancellable |
4596 | | * @error: Error |
4597 | | * |
4598 | | * Copy object named by @objtype and @checksum into @self from the |
4599 | | * source repository @source. If @trusted is %TRUE and both |
4600 | | * repositories are of the same type and on the same filesystem, |
4601 | | * this will simply be a fast Unix hard link operation. |
4602 | | * |
4603 | | * Otherwise, a copy will be performed. |
4604 | | * |
4605 | | * Since: 2016.5 |
4606 | | */ |
4607 | | gboolean |
4608 | | ostree_repo_import_object_from_with_trust (OstreeRepo *self, OstreeRepo *source, |
4609 | | OstreeObjectType objtype, const char *checksum, |
4610 | | gboolean trusted, GCancellable *cancellable, |
4611 | | GError **error) |
4612 | 0 | { |
4613 | | /* This just wraps a currently internal API, may make it public later */ |
4614 | 0 | OstreeRepoImportFlags flags = trusted ? _OSTREE_REPO_IMPORT_FLAGS_TRUSTED : 0; |
4615 | 0 | return _ostree_repo_import_object (self, source, objtype, checksum, flags, cancellable, error); |
4616 | 0 | } |
4617 | | |
4618 | | /** |
4619 | | * ostree_repo_query_object_storage_size: |
4620 | | * @self: Repo |
4621 | | * @objtype: Object type |
4622 | | * @sha256: Checksum |
4623 | | * @out_size: (out): Size in bytes object occupies physically |
4624 | | * @cancellable: Cancellable |
4625 | | * @error: Error |
4626 | | * |
4627 | | * Return the size in bytes of object with checksum @sha256, after any |
4628 | | * compression has been applied. |
4629 | | */ |
4630 | | gboolean |
4631 | | ostree_repo_query_object_storage_size (OstreeRepo *self, OstreeObjectType objtype, |
4632 | | const char *sha256, guint64 *out_size, |
4633 | | GCancellable *cancellable, GError **error) |
4634 | 0 | { |
4635 | 0 | char loose_path[_OSTREE_LOOSE_PATH_MAX]; |
4636 | 0 | _ostree_loose_path (loose_path, sha256, objtype, self->mode); |
4637 | 0 | int res; |
4638 | |
|
4639 | 0 | struct stat stbuf; |
4640 | 0 | res = TEMP_FAILURE_RETRY ( |
4641 | 0 | fstatat (self->objects_dir_fd, loose_path, &stbuf, AT_SYMLINK_NOFOLLOW)); |
4642 | 0 | if (res < 0 && errno == ENOENT && self->commit_stagedir.initialized) |
4643 | 0 | res = TEMP_FAILURE_RETRY ( |
4644 | 0 | fstatat (self->commit_stagedir.fd, loose_path, &stbuf, AT_SYMLINK_NOFOLLOW)); |
4645 | |
|
4646 | 0 | if (res < 0) |
4647 | 0 | return glnx_throw_errno_prefix (error, "Querying object %s.%s", sha256, |
4648 | 0 | ostree_object_type_to_string (objtype)); |
4649 | | |
4650 | 0 | *out_size = stbuf.st_size; |
4651 | 0 | return TRUE; |
4652 | 0 | } |
4653 | | |
4654 | | /** |
4655 | | * ostree_repo_load_variant_if_exists: |
4656 | | * @self: Repo |
4657 | | * @objtype: Object type |
4658 | | * @sha256: ASCII checksum |
4659 | | * @out_variant: (out) (nullable) (transfer full): Metadata |
4660 | | * @error: Error |
4661 | | * |
4662 | | * Attempt to load the metadata object @sha256 of type @objtype if it |
4663 | | * exists, storing the result in @out_variant. If it doesn't exist, |
4664 | | * @out_variant will be set to %NULL and the function will still |
4665 | | * return TRUE. |
4666 | | */ |
4667 | | gboolean |
4668 | | ostree_repo_load_variant_if_exists (OstreeRepo *self, OstreeObjectType objtype, const char *sha256, |
4669 | | GVariant **out_variant, GError **error) |
4670 | 0 | { |
4671 | 0 | return load_metadata_internal (self, objtype, sha256, FALSE, out_variant, NULL, NULL, NULL, NULL, |
4672 | 0 | error); |
4673 | 0 | } |
4674 | | |
4675 | | /** |
4676 | | * ostree_repo_load_variant: |
4677 | | * @self: Repo |
4678 | | * @objtype: Expected object type |
4679 | | * @sha256: Checksum string |
4680 | | * @out_variant: (out) (transfer full): Metadata object |
4681 | | * @error: Error |
4682 | | * |
4683 | | * Load the metadata object @sha256 of type @objtype, storing the |
4684 | | * result in @out_variant. |
4685 | | */ |
4686 | | gboolean |
4687 | | ostree_repo_load_variant (OstreeRepo *self, OstreeObjectType objtype, const char *sha256, |
4688 | | GVariant **out_variant, GError **error) |
4689 | 0 | { |
4690 | 0 | return load_metadata_internal (self, objtype, sha256, TRUE, out_variant, NULL, NULL, NULL, NULL, |
4691 | 0 | error); |
4692 | 0 | } |
4693 | | |
4694 | | /** |
4695 | | * ostree_repo_load_commit: |
4696 | | * @self: Repo |
4697 | | * @checksum: Commit checksum |
4698 | | * @out_commit: (out) (allow-none): Commit |
4699 | | * @out_state: (out) (allow-none): Commit state |
4700 | | * @error: Error |
4701 | | * |
4702 | | * A version of ostree_repo_load_variant() specialized to commits, |
4703 | | * capable of returning extended state information. Currently |
4704 | | * the only extended state is %OSTREE_REPO_COMMIT_STATE_PARTIAL, which |
4705 | | * means that only a sub-path of the commit is available. |
4706 | | */ |
4707 | | gboolean |
4708 | | ostree_repo_load_commit (OstreeRepo *self, const char *checksum, GVariant **out_variant, |
4709 | | OstreeRepoCommitState *out_state, GError **error) |
4710 | 0 | { |
4711 | 0 | return load_metadata_internal (self, OSTREE_OBJECT_TYPE_COMMIT, checksum, TRUE, out_variant, NULL, |
4712 | 0 | NULL, out_state, NULL, error); |
4713 | 0 | } |
4714 | | |
4715 | | static GHashTable * |
4716 | | repo_list_objects_impl (OstreeRepo *self, OstreeRepoListObjectsFlags flags, GVariant *dummy_value, |
4717 | | GCancellable *cancellable, GError **error) |
4718 | 0 | { |
4719 | 0 | g_assert (error == NULL || *error == NULL); |
4720 | 0 | g_assert (self->inited); |
4721 | | |
4722 | 0 | g_autoptr (GHashTable) ret_objects = g_hash_table_new_full ( |
4723 | 0 | ostree_hash_object_name, g_variant_equal, (GDestroyNotify)g_variant_unref, |
4724 | 0 | dummy_value ? (GDestroyNotify)g_variant_unref : NULL); |
4725 | |
|
4726 | 0 | if (flags & OSTREE_REPO_LIST_OBJECTS_ALL) |
4727 | 0 | flags |= (OSTREE_REPO_LIST_OBJECTS_LOOSE | OSTREE_REPO_LIST_OBJECTS_PACKED); |
4728 | |
|
4729 | 0 | if (flags & OSTREE_REPO_LIST_OBJECTS_LOOSE) |
4730 | 0 | { |
4731 | 0 | if (!list_loose_objects (self, dummy_value, ret_objects, NULL, cancellable, error)) |
4732 | 0 | return FALSE; |
4733 | 0 | if ((flags & OSTREE_REPO_LIST_OBJECTS_NO_PARENTS) == 0 && self->parent_repo) |
4734 | 0 | { |
4735 | 0 | if (!list_loose_objects (self->parent_repo, dummy_value, ret_objects, NULL, cancellable, |
4736 | 0 | error)) |
4737 | 0 | return FALSE; |
4738 | 0 | } |
4739 | 0 | } |
4740 | | |
4741 | 0 | if (flags & OSTREE_REPO_LIST_OBJECTS_PACKED) |
4742 | 0 | { |
4743 | | /* Nothing for now... */ |
4744 | 0 | } |
4745 | |
|
4746 | 0 | return g_steal_pointer (&ret_objects); |
4747 | 0 | } |
4748 | | |
4749 | | /* A currently-internal version of ostree_repo_list_objects which returns |
4750 | | * a set, and not a map (with a useless value). |
4751 | | */ |
4752 | | GHashTable * |
4753 | | ostree_repo_list_objects_set (OstreeRepo *self, OstreeRepoListObjectsFlags flags, |
4754 | | GCancellable *cancellable, GError **error) |
4755 | 0 | { |
4756 | 0 | return repo_list_objects_impl (self, flags, NULL, cancellable, error); |
4757 | 0 | } |
4758 | | |
4759 | | /* For unfortunate historical reasons we emit this dummy value. |
4760 | | * It was intended to provide additional information about the object (e.g. "is in a pack file") |
4761 | | * but we ended up not shipping pack files. |
4762 | | */ |
4763 | | static GVariant * |
4764 | | get_dummy_list_objects_variant (void) |
4765 | 0 | { |
4766 | 0 | return g_variant_ref_sink (g_variant_new ("(b@as)", TRUE, g_variant_new_strv (NULL, 0))); |
4767 | 0 | } |
4768 | | |
4769 | | /** |
4770 | | * ostree_repo_list_objects: |
4771 | | * @self: Repo |
4772 | | * @flags: Flags controlling enumeration |
4773 | | * @out_objects: (out) (transfer container) (element-type GVariant GVariant): |
4774 | | * Map of serialized object name to variant data |
4775 | | * @cancellable: Cancellable |
4776 | | * @error: Error |
4777 | | * |
4778 | | * This function synchronously enumerates all objects in the |
4779 | | * repository, returning data in @out_objects. @out_objects |
4780 | | * maps from keys returned by ostree_object_name_serialize() |
4781 | | * to #GVariant values of type %OSTREE_REPO_LIST_OBJECTS_VARIANT_TYPE. |
4782 | | * |
4783 | | * Returns: %TRUE on success, %FALSE on error, and @error will be set |
4784 | | */ |
4785 | | gboolean |
4786 | | ostree_repo_list_objects (OstreeRepo *self, OstreeRepoListObjectsFlags flags, |
4787 | | GHashTable **out_objects, GCancellable *cancellable, GError **error) |
4788 | 0 | { |
4789 | 0 | g_autoptr (GVariant) dummy_value = get_dummy_list_objects_variant (); |
4790 | 0 | g_autoptr (GHashTable) ret |
4791 | 0 | = repo_list_objects_impl (self, flags, dummy_value, cancellable, error); |
4792 | 0 | if (!ret) |
4793 | 0 | return FALSE; |
4794 | 0 | ot_transfer_out_value (out_objects, &ret); |
4795 | 0 | return TRUE; |
4796 | 0 | } |
4797 | | |
4798 | | /** |
4799 | | * ostree_repo_list_commit_objects_starting_with: |
4800 | | * @self: Repo |
4801 | | * @start: List commits starting with this checksum (empty string for all) |
4802 | | * @out_commits: (out) (transfer container) (element-type GVariant GVariant): |
4803 | | * Map of serialized commit name to variant data |
4804 | | * @cancellable: Cancellable |
4805 | | * @error: Error |
4806 | | * |
4807 | | * This function synchronously enumerates all commit objects starting |
4808 | | * with @start, returning data in @out_commits. |
4809 | | * |
4810 | | * To list all commit objects, provide the empty string `""` for @start. |
4811 | | * |
4812 | | * Returns: %TRUE on success, %FALSE on error, and @error will be set |
4813 | | */ |
4814 | | gboolean |
4815 | | ostree_repo_list_commit_objects_starting_with (OstreeRepo *self, const char *start, |
4816 | | GHashTable **out_commits, GCancellable *cancellable, |
4817 | | GError **error) |
4818 | 0 | { |
4819 | 0 | g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
4820 | 0 | g_return_val_if_fail (self->inited, FALSE); |
4821 | | |
4822 | 0 | g_autoptr (GHashTable) ret_commits |
4823 | 0 | = g_hash_table_new_full (ostree_hash_object_name, g_variant_equal, |
4824 | 0 | (GDestroyNotify)g_variant_unref, (GDestroyNotify)g_variant_unref); |
4825 | 0 | g_autoptr (GVariant) dummy_loose_object_variant = get_dummy_list_objects_variant (); |
4826 | |
|
4827 | 0 | if (!list_loose_objects (self, dummy_loose_object_variant, ret_commits, start, cancellable, |
4828 | 0 | error)) |
4829 | 0 | return FALSE; |
4830 | | |
4831 | 0 | if (self->parent_repo) |
4832 | 0 | { |
4833 | 0 | if (!list_loose_objects (self->parent_repo, dummy_loose_object_variant, ret_commits, start, |
4834 | 0 | cancellable, error)) |
4835 | 0 | return FALSE; |
4836 | 0 | } |
4837 | | |
4838 | 0 | ot_transfer_out_value (out_commits, &ret_commits); |
4839 | 0 | return TRUE; |
4840 | 0 | } |
4841 | | |
4842 | | /** |
4843 | | * ostree_repo_read_commit: |
4844 | | * @self: Repo |
4845 | | * @ref: Ref or ASCII checksum |
4846 | | * @out_root: (out) (optional): An #OstreeRepoFile corresponding to the root |
4847 | | * @out_commit: (out) (optional): The resolved commit checksum |
4848 | | * @cancellable: Cancellable |
4849 | | * @error: Error |
4850 | | * |
4851 | | * Load the content for @rev into @out_root. |
4852 | | */ |
4853 | | gboolean |
4854 | | ostree_repo_read_commit (OstreeRepo *self, const char *ref, GFile **out_root, char **out_commit, |
4855 | | GCancellable *cancellable, GError **error) |
4856 | 0 | { |
4857 | 0 | g_autofree char *resolved_commit = NULL; |
4858 | 0 | if (!ostree_repo_resolve_rev (self, ref, FALSE, &resolved_commit, error)) |
4859 | 0 | return FALSE; |
4860 | | |
4861 | 0 | g_autoptr (GFile) ret_root |
4862 | 0 | = (GFile *)_ostree_repo_file_new_for_commit (self, resolved_commit, error); |
4863 | 0 | if (!ret_root) |
4864 | 0 | return FALSE; |
4865 | | |
4866 | 0 | if (!ostree_repo_file_ensure_resolved ((OstreeRepoFile *)ret_root, error)) |
4867 | 0 | return FALSE; |
4868 | | |
4869 | 0 | ot_transfer_out_value (out_root, &ret_root); |
4870 | 0 | ot_transfer_out_value (out_commit, &resolved_commit); |
4871 | 0 | return TRUE; |
4872 | 0 | } |
4873 | | |
4874 | | /** |
4875 | | * ostree_repo_pull: |
4876 | | * @self: Repo |
4877 | | * @remote_name: Name of remote |
4878 | | * @refs_to_fetch: (array zero-terminated=1) (element-type utf8) (allow-none): Optional list of |
4879 | | * refs; if %NULL, fetch all configured refs |
4880 | | * @flags: Options controlling fetch behavior |
4881 | | * @progress: (allow-none): Progress |
4882 | | * @cancellable: Cancellable |
4883 | | * @error: Error |
4884 | | * |
4885 | | * Connect to the remote repository, fetching the specified set of |
4886 | | * refs @refs_to_fetch. For each ref that is changed, download the |
4887 | | * commit, all metadata, and all content objects, storing them safely |
4888 | | * on disk in @self. |
4889 | | * |
4890 | | * If @flags contains %OSTREE_REPO_PULL_FLAGS_MIRROR, and |
4891 | | * the @refs_to_fetch is %NULL, and the remote repository contains a |
4892 | | * summary file, then all refs will be fetched. |
4893 | | * |
4894 | | * If @flags contains %OSTREE_REPO_PULL_FLAGS_COMMIT_ONLY, then only the |
4895 | | * metadata for the commits in @refs_to_fetch is pulled. |
4896 | | * |
4897 | | * Warning: This API will iterate the thread default main context, |
4898 | | * which is a bug, but kept for compatibility reasons. If you want to |
4899 | | * avoid this, use g_main_context_push_thread_default() to push a new |
4900 | | * one around this call. |
4901 | | */ |
4902 | | gboolean |
4903 | | ostree_repo_pull (OstreeRepo *self, const char *remote_name, char **refs_to_fetch, |
4904 | | OstreeRepoPullFlags flags, OstreeAsyncProgress *progress, |
4905 | | GCancellable *cancellable, GError **error) |
4906 | 0 | { |
4907 | 0 | return ostree_repo_pull_one_dir (self, remote_name, NULL, refs_to_fetch, flags, progress, |
4908 | 0 | cancellable, error); |
4909 | 0 | } |
4910 | | |
4911 | | /** |
4912 | | * ostree_repo_pull_one_dir: |
4913 | | * @self: Repo |
4914 | | * @remote_name: Name of remote |
4915 | | * @dir_to_pull: Subdirectory path |
4916 | | * @refs_to_fetch: (array zero-terminated=1) (element-type utf8) (allow-none): Optional list of |
4917 | | * refs; if %NULL, fetch all configured refs |
4918 | | * @flags: Options controlling fetch behavior |
4919 | | * @progress: (allow-none): Progress |
4920 | | * @cancellable: Cancellable |
4921 | | * @error: Error |
4922 | | * |
4923 | | * This is similar to ostree_repo_pull(), but only fetches a single |
4924 | | * subpath. |
4925 | | */ |
4926 | | gboolean |
4927 | | ostree_repo_pull_one_dir (OstreeRepo *self, const char *remote_name, const char *dir_to_pull, |
4928 | | char **refs_to_fetch, OstreeRepoPullFlags flags, |
4929 | | OstreeAsyncProgress *progress, GCancellable *cancellable, GError **error) |
4930 | 0 | { |
4931 | 0 | GVariantBuilder builder; |
4932 | 0 | g_autoptr (GVariant) options = NULL; |
4933 | |
|
4934 | 0 | g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); |
4935 | |
|
4936 | 0 | if (dir_to_pull) |
4937 | 0 | g_variant_builder_add (&builder, "{s@v}", "subdir", |
4938 | 0 | g_variant_new_variant (g_variant_new_string (dir_to_pull))); |
4939 | 0 | g_variant_builder_add (&builder, "{s@v}", "flags", |
4940 | 0 | g_variant_new_variant (g_variant_new_int32 (flags))); |
4941 | 0 | if (refs_to_fetch) |
4942 | 0 | g_variant_builder_add ( |
4943 | 0 | &builder, "{s@v}", "refs", |
4944 | 0 | g_variant_new_variant (g_variant_new_strv ((const char *const *)refs_to_fetch, -1))); |
4945 | |
|
4946 | 0 | options = g_variant_ref_sink (g_variant_builder_end (&builder)); |
4947 | 0 | return ostree_repo_pull_with_options (self, remote_name, options, progress, cancellable, error); |
4948 | 0 | } |
4949 | | |
4950 | | /** |
4951 | | * _formatted_time_remaining_from_seconds: |
4952 | | * @seconds_remaining: Estimated number of seconds remaining. |
4953 | | * |
4954 | | * Returns a strings showing the number of days, hours, minutes |
4955 | | * and seconds remaining. |
4956 | | **/ |
4957 | | static char * |
4958 | | _formatted_time_remaining_from_seconds (guint64 seconds_remaining) |
4959 | 0 | { |
4960 | 0 | guint64 minutes_remaining = seconds_remaining / 60; |
4961 | 0 | guint64 hours_remaining = minutes_remaining / 60; |
4962 | 0 | guint64 days_remaining = hours_remaining / 24; |
4963 | |
|
4964 | 0 | GString *description = g_string_new (NULL); |
4965 | |
|
4966 | 0 | if (days_remaining) |
4967 | 0 | g_string_append_printf (description, "%" G_GUINT64_FORMAT " days ", days_remaining); |
4968 | |
|
4969 | 0 | if (hours_remaining) |
4970 | 0 | g_string_append_printf (description, "%" G_GUINT64_FORMAT " hours ", hours_remaining % 24); |
4971 | |
|
4972 | 0 | if (minutes_remaining) |
4973 | 0 | g_string_append_printf (description, "%" G_GUINT64_FORMAT " minutes ", minutes_remaining % 60); |
4974 | |
|
4975 | 0 | g_string_append_printf (description, "%" G_GUINT64_FORMAT " seconds ", seconds_remaining % 60); |
4976 | |
|
4977 | 0 | return g_string_free (description, FALSE); |
4978 | 0 | } |
4979 | | |
4980 | | /** |
4981 | | * ostree_repo_pull_default_console_progress_changed: |
4982 | | * @progress: Async progress |
4983 | | * @user_data: (allow-none): User data |
4984 | | * |
4985 | | * Convenient "changed" callback for use with |
4986 | | * ostree_async_progress_new_and_connect() when pulling from a remote |
4987 | | * repository. |
4988 | | * |
4989 | | * Depending on the state of the #OstreeAsyncProgress, either displays a |
4990 | | * custom status message, or else outstanding fetch progress in bytes/sec, |
4991 | | * or else outstanding content or metadata writes to the repository in |
4992 | | * number of objects. |
4993 | | * |
4994 | | * Compatibility note: this function previously assumed that @user_data |
4995 | | * was a pointer to a #GSConsole instance. This is no longer the case, |
4996 | | * and @user_data is ignored. |
4997 | | **/ |
4998 | | void |
4999 | | ostree_repo_pull_default_console_progress_changed (OstreeAsyncProgress *progress, |
5000 | | gpointer user_data) |
5001 | 0 | { |
5002 | 0 | g_autofree char *status = NULL; |
5003 | 0 | gboolean caught_error, scanning; |
5004 | 0 | guint outstanding_fetches; |
5005 | 0 | guint outstanding_metadata_fetches; |
5006 | 0 | guint outstanding_writes; |
5007 | 0 | guint n_scanned_metadata; |
5008 | 0 | guint fetched_delta_parts; |
5009 | 0 | guint total_delta_parts; |
5010 | 0 | guint fetched_delta_part_fallbacks; |
5011 | 0 | guint total_delta_part_fallbacks; |
5012 | |
|
5013 | 0 | g_autoptr (GString) buf = g_string_new (""); |
5014 | |
|
5015 | 0 | ostree_async_progress_get ( |
5016 | 0 | progress, "outstanding-fetches", "u", &outstanding_fetches, "outstanding-metadata-fetches", |
5017 | 0 | "u", &outstanding_metadata_fetches, "outstanding-writes", "u", &outstanding_writes, |
5018 | 0 | "caught-error", "b", &caught_error, "scanning", "u", &scanning, "scanned-metadata", "u", |
5019 | 0 | &n_scanned_metadata, "fetched-delta-parts", "u", &fetched_delta_parts, "total-delta-parts", |
5020 | 0 | "u", &total_delta_parts, "fetched-delta-fallbacks", "u", &fetched_delta_part_fallbacks, |
5021 | 0 | "total-delta-fallbacks", "u", &total_delta_part_fallbacks, "status", "s", &status, NULL); |
5022 | |
|
5023 | 0 | if (*status != '\0') |
5024 | 0 | { |
5025 | 0 | g_string_append (buf, status); |
5026 | 0 | } |
5027 | 0 | else if (caught_error) |
5028 | 0 | { |
5029 | 0 | g_string_append_printf (buf, "Caught error, waiting for outstanding tasks"); |
5030 | 0 | } |
5031 | 0 | else if (outstanding_fetches) |
5032 | 0 | { |
5033 | 0 | guint64 bytes_transferred, start_time, total_delta_part_size; |
5034 | 0 | guint fetched, metadata_fetched, requested; |
5035 | 0 | guint64 current_time = g_get_monotonic_time (); |
5036 | 0 | g_autofree char *formatted_bytes_transferred = NULL; |
5037 | 0 | g_autofree char *formatted_bytes_sec = NULL; |
5038 | 0 | guint64 bytes_sec; |
5039 | | |
5040 | | /* Note: This is not atomic wrt the above getter call. */ |
5041 | 0 | ostree_async_progress_get (progress, "bytes-transferred", "t", &bytes_transferred, "fetched", |
5042 | 0 | "u", &fetched, "metadata-fetched", "u", &metadata_fetched, |
5043 | 0 | "requested", "u", &requested, "start-time", "t", &start_time, |
5044 | 0 | "total-delta-part-size", "t", &total_delta_part_size, NULL); |
5045 | |
|
5046 | 0 | formatted_bytes_transferred = g_format_size_full (bytes_transferred, 0); |
5047 | | |
5048 | | /* Ignore the first second, or when we haven't transferred any |
5049 | | * data, since those could cause divide by zero below. |
5050 | | */ |
5051 | 0 | if ((current_time - start_time) < G_USEC_PER_SEC || bytes_transferred == 0) |
5052 | 0 | { |
5053 | 0 | bytes_sec = 0; |
5054 | 0 | formatted_bytes_sec = g_strdup ("-"); |
5055 | 0 | } |
5056 | 0 | else |
5057 | 0 | { |
5058 | 0 | bytes_sec = bytes_transferred / ((current_time - start_time) / G_USEC_PER_SEC); |
5059 | 0 | formatted_bytes_sec = g_format_size (bytes_sec); |
5060 | 0 | } |
5061 | | |
5062 | | /* Are we doing deltas? If so, we can be more accurate */ |
5063 | 0 | if (total_delta_parts > 0) |
5064 | 0 | { |
5065 | 0 | guint64 fetched_delta_part_size |
5066 | 0 | = ostree_async_progress_get_uint64 (progress, "fetched-delta-part-size"); |
5067 | 0 | g_autofree char *formatted_fetched = NULL; |
5068 | 0 | g_autofree char *formatted_total = NULL; |
5069 | | |
5070 | | /* Here we merge together deltaparts + fallbacks to avoid bloating the text UI */ |
5071 | 0 | fetched_delta_parts += fetched_delta_part_fallbacks; |
5072 | 0 | total_delta_parts += total_delta_part_fallbacks; |
5073 | |
|
5074 | 0 | formatted_fetched = g_format_size (fetched_delta_part_size); |
5075 | 0 | formatted_total = g_format_size (total_delta_part_size); |
5076 | |
|
5077 | 0 | if (bytes_sec > 0) |
5078 | 0 | { |
5079 | 0 | guint64 est_time_remaining = 0; |
5080 | 0 | if (total_delta_part_size > fetched_delta_part_size) |
5081 | 0 | est_time_remaining = (total_delta_part_size - fetched_delta_part_size) / bytes_sec; |
5082 | 0 | g_autofree char *formatted_est_time_remaining |
5083 | 0 | = _formatted_time_remaining_from_seconds (est_time_remaining); |
5084 | | /* No space between %s and remaining, since formatted_est_time_remaining has a |
5085 | | * trailing space */ |
5086 | 0 | g_string_append_printf (buf, "Receiving delta parts: %u/%u %s/%s %s/s %sremaining", |
5087 | 0 | fetched_delta_parts, total_delta_parts, formatted_fetched, |
5088 | 0 | formatted_total, formatted_bytes_sec, |
5089 | 0 | formatted_est_time_remaining); |
5090 | 0 | } |
5091 | 0 | else |
5092 | 0 | { |
5093 | 0 | g_string_append_printf (buf, "Receiving delta parts: %u/%u %s/%s", |
5094 | 0 | fetched_delta_parts, total_delta_parts, formatted_fetched, |
5095 | 0 | formatted_total); |
5096 | 0 | } |
5097 | 0 | } |
5098 | 0 | else if (scanning || outstanding_metadata_fetches) |
5099 | 0 | { |
5100 | 0 | g_string_append_printf (buf, "Receiving metadata objects: %u/(estimating) %s/s %s", |
5101 | 0 | metadata_fetched, formatted_bytes_sec, |
5102 | 0 | formatted_bytes_transferred); |
5103 | 0 | } |
5104 | 0 | else |
5105 | 0 | { |
5106 | 0 | g_string_append_printf (buf, "Receiving objects: %u%% (%u/%u) %s/s %s", |
5107 | 0 | (guint)((((double)fetched) / requested) * 100), fetched, |
5108 | 0 | requested, formatted_bytes_sec, formatted_bytes_transferred); |
5109 | 0 | } |
5110 | 0 | } |
5111 | 0 | else if (outstanding_writes) |
5112 | 0 | { |
5113 | 0 | g_string_append_printf (buf, "Writing objects: %u", outstanding_writes); |
5114 | 0 | } |
5115 | 0 | else |
5116 | 0 | { |
5117 | 0 | g_string_append_printf (buf, "Scanning metadata: %u", n_scanned_metadata); |
5118 | 0 | } |
5119 | |
|
5120 | 0 | glnx_console_text (buf->str); |
5121 | 0 | } |
5122 | | |
5123 | | /** |
5124 | | * ostree_repo_append_gpg_signature: |
5125 | | * @self: Self |
5126 | | * @commit_checksum: SHA256 of given commit to sign |
5127 | | * @signature_bytes: Signature data |
5128 | | * @cancellable: A #GCancellable |
5129 | | * @error: a #GError |
5130 | | * |
5131 | | * Append a GPG signature to a commit. |
5132 | | */ |
5133 | | gboolean |
5134 | | ostree_repo_append_gpg_signature (OstreeRepo *self, const gchar *commit_checksum, |
5135 | | GBytes *signature_bytes, GCancellable *cancellable, |
5136 | | GError **error) |
5137 | 0 | { |
5138 | 0 | g_autoptr (GVariant) metadata = NULL; |
5139 | 0 | if (!ostree_repo_read_commit_detached_metadata (self, commit_checksum, &metadata, cancellable, |
5140 | 0 | error)) |
5141 | 0 | return FALSE; |
5142 | | |
5143 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5144 | 0 | g_autoptr (GVariant) new_metadata |
5145 | 0 | = _ostree_detached_metadata_append_gpg_sig (metadata, signature_bytes); |
5146 | |
|
5147 | 0 | if (!ostree_repo_write_commit_detached_metadata (self, commit_checksum, new_metadata, cancellable, |
5148 | 0 | error)) |
5149 | 0 | return FALSE; |
5150 | | |
5151 | 0 | return TRUE; |
5152 | | #else |
5153 | | return glnx_throw (error, "GPG feature is disabled in a build time"); |
5154 | | #endif /* OSTREE_DISABLE_GPGME */ |
5155 | 0 | } |
5156 | | |
5157 | | #ifndef OSTREE_DISABLE_GPGME |
5158 | | static gboolean |
5159 | | sign_data (OstreeRepo *self, GBytes *input_data, const gchar *key_id, const gchar *homedir, |
5160 | | GBytes **out_signature, GCancellable *cancellable, GError **error) |
5161 | 0 | { |
5162 | 0 | g_auto (GLnxTmpfile) tmpf = { |
5163 | 0 | 0, |
5164 | 0 | }; |
5165 | 0 | if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_RDWR | O_CLOEXEC, &tmpf, error)) |
5166 | 0 | return FALSE; |
5167 | 0 | g_autoptr (GOutputStream) tmp_signature_output = g_unix_output_stream_new (tmpf.fd, FALSE); |
5168 | |
|
5169 | 0 | g_auto (gpgme_ctx_t) context = ot_gpgme_new_ctx (homedir, error); |
5170 | 0 | if (!context) |
5171 | 0 | return FALSE; |
5172 | | |
5173 | | /* Get the secret keys with the given key id */ |
5174 | 0 | g_auto (gpgme_key_t) key = NULL; |
5175 | 0 | gpgme_error_t err = gpgme_get_key (context, key_id, &key, 1); |
5176 | 0 | if (gpgme_err_code (err) == GPG_ERR_EOF) |
5177 | 0 | return glnx_throw (error, "No gpg key found with ID %s (homedir: %s)", key_id, |
5178 | 0 | homedir ? homedir : "<default>"); |
5179 | 0 | else if (gpgme_err_code (err) == GPG_ERR_AMBIGUOUS_NAME) |
5180 | 0 | { |
5181 | 0 | return glnx_throw (error, |
5182 | 0 | "gpg key id %s ambiguous (homedir: %s). Try the fingerprint instead", |
5183 | 0 | key_id, homedir ? homedir : "<default>"); |
5184 | 0 | } |
5185 | 0 | else if (err != GPG_ERR_NO_ERROR) |
5186 | 0 | return ot_gpgme_throw (err, error, "Unable to lookup key ID %s", key_id); |
5187 | | |
5188 | | /* Add the key to the context as a signer */ |
5189 | 0 | if ((err = gpgme_signers_add (context, key)) != GPG_ERR_NO_ERROR) |
5190 | 0 | return ot_gpgme_throw (err, error, "Error signing commit"); |
5191 | | |
5192 | | /* Get a gpg buffer from the commit */ |
5193 | 0 | g_auto (gpgme_data_t) commit_buffer = NULL; |
5194 | 0 | gsize len; |
5195 | 0 | const char *buf = g_bytes_get_data (input_data, &len); |
5196 | 0 | if ((err = gpgme_data_new_from_mem (&commit_buffer, buf, len, FALSE)) != GPG_ERR_NO_ERROR) |
5197 | 0 | return ot_gpgme_throw (err, error, "Failed to create buffer from commit file"); |
5198 | | |
5199 | | /* Sign it */ |
5200 | 0 | g_auto (gpgme_data_t) signature_buffer = ot_gpgme_data_output (tmp_signature_output); |
5201 | 0 | if ((err = gpgme_op_sign (context, commit_buffer, signature_buffer, GPGME_SIG_MODE_DETACH)) |
5202 | 0 | != GPG_ERR_NO_ERROR) |
5203 | 0 | return ot_gpgme_throw (err, error, "Failure signing commit file"); |
5204 | 0 | if (!g_output_stream_close (tmp_signature_output, cancellable, error)) |
5205 | 0 | return FALSE; |
5206 | | |
5207 | | /* Return a mmap() reference */ |
5208 | 0 | g_autoptr (GMappedFile) signature_file = g_mapped_file_new_from_fd (tmpf.fd, FALSE, error); |
5209 | 0 | if (!signature_file) |
5210 | 0 | return FALSE; |
5211 | | |
5212 | 0 | if (out_signature) |
5213 | 0 | *out_signature = g_mapped_file_get_bytes (signature_file); |
5214 | 0 | return TRUE; |
5215 | 0 | } |
5216 | | #endif /* OSTREE_DISABLE_GPGME */ |
5217 | | |
5218 | | /** |
5219 | | * ostree_repo_sign_commit: |
5220 | | * @self: Self |
5221 | | * @commit_checksum: SHA256 of given commit to sign |
5222 | | * @key_id: Use this GPG key id |
5223 | | * @homedir: (allow-none): GPG home directory, or %NULL |
5224 | | * @cancellable: A #GCancellable |
5225 | | * @error: a #GError |
5226 | | * |
5227 | | * Add a GPG signature to a commit. |
5228 | | */ |
5229 | | gboolean |
5230 | | ostree_repo_sign_commit (OstreeRepo *self, const gchar *commit_checksum, const gchar *key_id, |
5231 | | const gchar *homedir, GCancellable *cancellable, GError **error) |
5232 | 0 | { |
5233 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5234 | 0 | g_autoptr (GBytes) commit_data = NULL; |
5235 | 0 | g_autoptr (GBytes) signature = NULL; |
5236 | |
|
5237 | 0 | g_autoptr (GVariant) commit_variant = NULL; |
5238 | 0 | if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, commit_checksum, &commit_variant, |
5239 | 0 | error)) |
5240 | 0 | return glnx_prefix_error (error, "Failed to read commit"); |
5241 | | |
5242 | 0 | g_autoptr (GVariant) old_metadata = NULL; |
5243 | 0 | if (!ostree_repo_read_commit_detached_metadata (self, commit_checksum, &old_metadata, cancellable, |
5244 | 0 | error)) |
5245 | 0 | return glnx_prefix_error (error, "Failed to read detached metadata"); |
5246 | | |
5247 | 0 | commit_data = g_variant_get_data_as_bytes (commit_variant); |
5248 | | |
5249 | | /* The verify operation is merely to parse any existing signatures to |
5250 | | * check if the commit has already been signed with the given key ID. |
5251 | | * We want to avoid storing duplicate signatures in the metadata. We |
5252 | | * pass the homedir so that the signing key can be imported, allowing |
5253 | | * subkey signatures to be recognised. */ |
5254 | 0 | g_autoptr (GError) local_error = NULL; |
5255 | 0 | g_autoptr (GFile) verify_keydir = NULL; |
5256 | 0 | if (homedir != NULL) |
5257 | 0 | verify_keydir = g_file_new_for_path (homedir); |
5258 | 0 | g_autoptr (OstreeGpgVerifyResult) result = _ostree_repo_gpg_verify_with_metadata ( |
5259 | 0 | self, commit_data, old_metadata, NULL, verify_keydir, NULL, cancellable, &local_error); |
5260 | 0 | if (!result) |
5261 | 0 | { |
5262 | | /* "Not found" just means the commit is not yet signed. That's okay. */ |
5263 | 0 | if (g_error_matches (local_error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE)) |
5264 | 0 | { |
5265 | 0 | g_clear_error (&local_error); |
5266 | 0 | } |
5267 | 0 | else |
5268 | 0 | return g_propagate_error (error, g_steal_pointer (&local_error)), FALSE; |
5269 | 0 | } |
5270 | 0 | else if (ostree_gpg_verify_result_lookup (result, key_id, NULL)) |
5271 | 0 | { |
5272 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS, "Commit is already signed with GPG key %s", |
5273 | 0 | key_id); |
5274 | 0 | return FALSE; |
5275 | 0 | } |
5276 | | |
5277 | 0 | if (!sign_data (self, commit_data, key_id, homedir, &signature, cancellable, error)) |
5278 | 0 | return FALSE; |
5279 | | |
5280 | 0 | g_autoptr (GVariant) new_metadata |
5281 | 0 | = _ostree_detached_metadata_append_gpg_sig (old_metadata, signature); |
5282 | |
|
5283 | 0 | if (!ostree_repo_write_commit_detached_metadata (self, commit_checksum, new_metadata, cancellable, |
5284 | 0 | error)) |
5285 | 0 | return FALSE; |
5286 | | |
5287 | 0 | return TRUE; |
5288 | | #else |
5289 | | /* FIXME: Return false until refactoring */ |
5290 | | return glnx_throw (error, "GPG feature is disabled in a build time"); |
5291 | | #endif /* OSTREE_DISABLE_GPGME */ |
5292 | 0 | } |
5293 | | |
5294 | | /** |
5295 | | * ostree_repo_sign_delta: |
5296 | | * @self: Self |
5297 | | * @from_commit: From commit |
5298 | | * @to_commit: To commit |
5299 | | * @key_id: key id |
5300 | | * @homedir: homedir |
5301 | | * @cancellable: cancellable |
5302 | | * @error: error |
5303 | | * |
5304 | | * This function is deprecated, sign the summary file instead. |
5305 | | * Add a GPG signature to a static delta. |
5306 | | */ |
5307 | | gboolean |
5308 | | ostree_repo_sign_delta (OstreeRepo *self, const gchar *from_commit, const gchar *to_commit, |
5309 | | const gchar *key_id, const gchar *homedir, GCancellable *cancellable, |
5310 | | GError **error) |
5311 | 0 | { |
5312 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "ostree_repo_sign_delta is deprecated"); |
5313 | 0 | return FALSE; |
5314 | 0 | } |
5315 | | |
5316 | | static gboolean |
5317 | | _ostree_repo_add_gpg_signature_summary_at (OstreeRepo *self, int dir_fd, const gchar **key_id, |
5318 | | const gchar *homedir, GCancellable *cancellable, |
5319 | | GError **error) |
5320 | 0 | { |
5321 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5322 | 0 | glnx_autofd int fd = -1; |
5323 | 0 | if (!glnx_openat_rdonly (dir_fd, "summary", TRUE, &fd, error)) |
5324 | 0 | return FALSE; |
5325 | 0 | g_autoptr (GBytes) summary_data = ot_fd_readall_or_mmap (fd, 0, error); |
5326 | 0 | if (!summary_data) |
5327 | 0 | return FALSE; |
5328 | | /* Note that fd is reused below */ |
5329 | 0 | glnx_close_fd (&fd); |
5330 | |
|
5331 | 0 | g_autoptr (GVariant) metadata = NULL; |
5332 | 0 | if (!ot_openat_ignore_enoent (dir_fd, "summary.sig", &fd, error)) |
5333 | 0 | return FALSE; |
5334 | 0 | if (fd >= 0) |
5335 | 0 | { |
5336 | 0 | if (!ot_variant_read_fd (fd, 0, G_VARIANT_TYPE (OSTREE_SUMMARY_SIG_GVARIANT_STRING), FALSE, |
5337 | 0 | &metadata, error)) |
5338 | 0 | return FALSE; |
5339 | 0 | } |
5340 | | |
5341 | 0 | for (guint i = 0; key_id[i]; i++) |
5342 | 0 | { |
5343 | 0 | g_autoptr (GBytes) signature_data = NULL; |
5344 | 0 | if (!sign_data (self, summary_data, key_id[i], homedir, &signature_data, cancellable, error)) |
5345 | 0 | return FALSE; |
5346 | | |
5347 | 0 | g_autoptr (GVariant) old_metadata = g_steal_pointer (&metadata); |
5348 | 0 | metadata = _ostree_detached_metadata_append_gpg_sig (old_metadata, signature_data); |
5349 | 0 | } |
5350 | | |
5351 | 0 | g_autoptr (GVariant) normalized = g_variant_get_normal_form (metadata); |
5352 | |
|
5353 | 0 | if (!_ostree_repo_file_replace_contents (self, dir_fd, "summary.sig", |
5354 | 0 | g_variant_get_data (normalized), |
5355 | 0 | g_variant_get_size (normalized), cancellable, error)) |
5356 | 0 | return FALSE; |
5357 | | |
5358 | 0 | return TRUE; |
5359 | | #else |
5360 | | return glnx_throw (error, "GPG feature is disabled at build time"); |
5361 | | #endif /* OSTREE_DISABLE_GPGME */ |
5362 | 0 | } |
5363 | | |
5364 | | /** |
5365 | | * ostree_repo_add_gpg_signature_summary: |
5366 | | * @self: Self |
5367 | | * @key_id: (array zero-terminated=1) (element-type utf8): NULL-terminated array of GPG keys. |
5368 | | * @homedir: (allow-none): GPG home directory, or %NULL |
5369 | | * @cancellable: A #GCancellable |
5370 | | * @error: a #GError |
5371 | | * |
5372 | | * Add a GPG signature to a summary file. |
5373 | | */ |
5374 | | gboolean |
5375 | | ostree_repo_add_gpg_signature_summary (OstreeRepo *self, const gchar **key_id, const gchar *homedir, |
5376 | | GCancellable *cancellable, GError **error) |
5377 | 0 | { |
5378 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5379 | 0 | return _ostree_repo_add_gpg_signature_summary_at (self, self->repo_dir_fd, key_id, homedir, |
5380 | 0 | cancellable, error); |
5381 | | #else |
5382 | | return glnx_throw (error, "GPG feature is disabled in a build time"); |
5383 | | #endif /* OSTREE_DISABLE_GPGME */ |
5384 | 0 | } |
5385 | | |
5386 | | /** |
5387 | | * ostree_repo_gpg_sign_data: |
5388 | | * @self: Self |
5389 | | * @data: Data as a #GBytes |
5390 | | * @old_signatures: (nullable): Existing signatures to append to (or %NULL) |
5391 | | * @key_id: (array zero-terminated=1) (element-type utf8): NULL-terminated array of GPG keys. |
5392 | | * @homedir: (nullable): GPG home directory, or %NULL |
5393 | | * @out_signatures: (out): in case of success will contain signature |
5394 | | * @cancellable: A #GCancellable |
5395 | | * @error: a #GError |
5396 | | * |
5397 | | * Sign the given @data with the specified keys in @key_id. Similar to |
5398 | | * ostree_repo_add_gpg_signature_summary() but can be used on any |
5399 | | * data. |
5400 | | * |
5401 | | * You can use ostree_repo_gpg_verify_data() to verify the signatures. |
5402 | | * |
5403 | | * Returns: %TRUE if @data has been signed successfully, |
5404 | | * %FALSE in case of error (@error will contain the reason). |
5405 | | * |
5406 | | * Since: 2020.8 |
5407 | | */ |
5408 | | gboolean |
5409 | | ostree_repo_gpg_sign_data (OstreeRepo *self, GBytes *data, GBytes *old_signatures, |
5410 | | const gchar **key_id, const gchar *homedir, GBytes **out_signatures, |
5411 | | GCancellable *cancellable, GError **error) |
5412 | 0 | { |
5413 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5414 | 0 | g_autoptr (GVariant) metadata = NULL; |
5415 | 0 | g_autoptr (GVariant) res = NULL; |
5416 | |
|
5417 | 0 | if (old_signatures) |
5418 | 0 | metadata = g_variant_ref_sink (g_variant_new_from_bytes ( |
5419 | 0 | G_VARIANT_TYPE (OSTREE_SUMMARY_SIG_GVARIANT_STRING), old_signatures, FALSE)); |
5420 | |
|
5421 | 0 | for (guint i = 0; key_id[i]; i++) |
5422 | 0 | { |
5423 | 0 | g_autoptr (GBytes) signature_data = NULL; |
5424 | 0 | if (!sign_data (self, data, key_id[i], homedir, &signature_data, cancellable, error)) |
5425 | 0 | return FALSE; |
5426 | | |
5427 | 0 | g_autoptr (GVariant) old_metadata = g_steal_pointer (&metadata); |
5428 | 0 | metadata = _ostree_detached_metadata_append_gpg_sig (old_metadata, signature_data); |
5429 | 0 | } |
5430 | | |
5431 | 0 | res = g_variant_get_normal_form (metadata); |
5432 | 0 | *out_signatures = g_variant_get_data_as_bytes (res); |
5433 | 0 | return TRUE; |
5434 | | #else |
5435 | | return glnx_throw (error, "GPG feature is disabled in a build time"); |
5436 | | #endif /* OSTREE_DISABLE_GPGME */ |
5437 | 0 | } |
5438 | | |
5439 | | #ifndef OSTREE_DISABLE_GPGME |
5440 | | /* Special remote for _ostree_repo_gpg_verify_with_metadata() */ |
5441 | | static const char *OSTREE_ALL_REMOTES = "__OSTREE_ALL_REMOTES__"; |
5442 | | |
5443 | | /* Look for a keyring for @remote in the repo itself, or in |
5444 | | * /etc/ostree/remotes.d. |
5445 | | */ |
5446 | | static gboolean |
5447 | | find_keyring (OstreeRepo *self, OstreeRemote *remote, GBytes **ret_bytes, GCancellable *cancellable, |
5448 | | GError **error) |
5449 | 0 | { |
5450 | 0 | glnx_autofd int fd = -1; |
5451 | 0 | if (!ot_openat_ignore_enoent (self->repo_dir_fd, remote->keyring, &fd, error)) |
5452 | 0 | return FALSE; |
5453 | | |
5454 | 0 | if (fd != -1) |
5455 | 0 | { |
5456 | 0 | GBytes *ret = glnx_fd_readall_bytes (fd, cancellable, error); |
5457 | 0 | if (!ret) |
5458 | 0 | return FALSE; |
5459 | 0 | *ret_bytes = ret; |
5460 | 0 | return TRUE; |
5461 | 0 | } |
5462 | | |
5463 | 0 | g_autoptr (GFile) remotes_d = get_remotes_d_dir (self, NULL); |
5464 | 0 | if (remotes_d) |
5465 | 0 | { |
5466 | 0 | g_autoptr (GFile) child = g_file_get_child (remotes_d, remote->keyring); |
5467 | |
|
5468 | 0 | if (!ot_openat_ignore_enoent (AT_FDCWD, gs_file_get_path_cached (child), &fd, error)) |
5469 | 0 | return FALSE; |
5470 | | |
5471 | 0 | if (fd != -1) |
5472 | 0 | { |
5473 | 0 | GBytes *ret = glnx_fd_readall_bytes (fd, cancellable, error); |
5474 | 0 | if (!ret) |
5475 | 0 | return FALSE; |
5476 | 0 | *ret_bytes = ret; |
5477 | 0 | return TRUE; |
5478 | 0 | } |
5479 | 0 | } |
5480 | | |
5481 | 0 | if (self->parent_repo) |
5482 | 0 | return find_keyring (self->parent_repo, remote, ret_bytes, cancellable, error); |
5483 | | |
5484 | 0 | *ret_bytes = NULL; |
5485 | 0 | return TRUE; |
5486 | 0 | } |
5487 | | |
5488 | | static gboolean |
5489 | | _ostree_repo_gpg_prepare_verifier (OstreeRepo *self, const gchar *remote_name, GFile *keyringdir, |
5490 | | GFile *extra_keyring, gboolean add_global_keyrings, |
5491 | | OstreeGpgVerifier **out_verifier, GCancellable *cancellable, |
5492 | | GError **error) |
5493 | 0 | { |
5494 | 0 | g_autoptr (OstreeGpgVerifier) verifier = _ostree_gpg_verifier_new (); |
5495 | |
|
5496 | 0 | if (remote_name == OSTREE_ALL_REMOTES) |
5497 | 0 | { |
5498 | | /* Add all available remote keyring files. */ |
5499 | |
|
5500 | 0 | if (!_ostree_gpg_verifier_add_keyring_dir_at (verifier, self->repo_dir_fd, ".", cancellable, |
5501 | 0 | error)) |
5502 | 0 | return FALSE; |
5503 | 0 | } |
5504 | 0 | else if (remote_name != NULL) |
5505 | 0 | { |
5506 | | /* Add the remote's keyring file if it exists. */ |
5507 | |
|
5508 | 0 | g_autoptr (OstreeRemote) remote = NULL; |
5509 | |
|
5510 | 0 | remote = _ostree_repo_get_remote_inherited (self, remote_name, error); |
5511 | 0 | if (remote == NULL) |
5512 | 0 | return FALSE; |
5513 | | |
5514 | 0 | g_autoptr (GBytes) keyring_data = NULL; |
5515 | 0 | if (!find_keyring (self, remote, &keyring_data, cancellable, error)) |
5516 | 0 | return FALSE; |
5517 | | |
5518 | 0 | if (keyring_data != NULL) |
5519 | 0 | { |
5520 | 0 | _ostree_gpg_verifier_add_keyring_data (verifier, keyring_data, remote->keyring); |
5521 | 0 | add_global_keyrings = FALSE; |
5522 | 0 | } |
5523 | |
|
5524 | 0 | g_auto (GStrv) gpgkeypath_list = NULL; |
5525 | |
|
5526 | 0 | if (!ot_keyfile_get_string_list_with_separator_choice ( |
5527 | 0 | remote->options, remote->group, "gpgkeypath", ";,", &gpgkeypath_list, error)) |
5528 | 0 | return FALSE; |
5529 | | |
5530 | 0 | if (gpgkeypath_list) |
5531 | 0 | { |
5532 | 0 | for (char **iter = gpgkeypath_list; *iter != NULL; ++iter) |
5533 | 0 | if (!_ostree_gpg_verifier_add_keyfile_path (verifier, *iter, cancellable, error)) |
5534 | 0 | return FALSE; |
5535 | 0 | } |
5536 | 0 | } |
5537 | | |
5538 | 0 | if (add_global_keyrings) |
5539 | 0 | { |
5540 | | /* Use the deprecated global keyring directory. */ |
5541 | 0 | if (!_ostree_gpg_verifier_add_global_keyring_dir (verifier, cancellable, error)) |
5542 | 0 | return FALSE; |
5543 | 0 | } |
5544 | | |
5545 | 0 | if (keyringdir) |
5546 | 0 | { |
5547 | 0 | if (!_ostree_gpg_verifier_add_keyring_dir (verifier, keyringdir, cancellable, error)) |
5548 | 0 | return FALSE; |
5549 | 0 | } |
5550 | 0 | if (extra_keyring != NULL) |
5551 | 0 | { |
5552 | 0 | _ostree_gpg_verifier_add_keyring_file (verifier, extra_keyring); |
5553 | 0 | } |
5554 | |
|
5555 | 0 | if (out_verifier != NULL) |
5556 | 0 | *out_verifier = g_steal_pointer (&verifier); |
5557 | |
|
5558 | 0 | return TRUE; |
5559 | 0 | } |
5560 | | |
5561 | | static OstreeGpgVerifyResult * |
5562 | | _ostree_repo_gpg_verify_data_internal (OstreeRepo *self, const gchar *remote_name, GBytes *data, |
5563 | | GBytes *signatures, GFile *keyringdir, GFile *extra_keyring, |
5564 | | GCancellable *cancellable, GError **error) |
5565 | 0 | { |
5566 | 0 | g_autoptr (OstreeGpgVerifier) verifier = NULL; |
5567 | 0 | if (!_ostree_repo_gpg_prepare_verifier (self, remote_name, keyringdir, extra_keyring, TRUE, |
5568 | 0 | &verifier, cancellable, error)) |
5569 | 0 | return NULL; |
5570 | | |
5571 | 0 | return _ostree_gpg_verifier_check_signature (verifier, data, signatures, cancellable, error); |
5572 | 0 | } |
5573 | | |
5574 | | OstreeGpgVerifyResult * |
5575 | | _ostree_repo_gpg_verify_with_metadata (OstreeRepo *self, GBytes *signed_data, GVariant *metadata, |
5576 | | const char *remote_name, GFile *keyringdir, |
5577 | | GFile *extra_keyring, GCancellable *cancellable, |
5578 | | GError **error) |
5579 | 0 | { |
5580 | 0 | g_autoptr (GVariant) signaturedata = NULL; |
5581 | 0 | GByteArray *buffer; |
5582 | 0 | GVariantIter iter; |
5583 | 0 | GVariant *child; |
5584 | 0 | g_autoptr (GBytes) signatures = NULL; |
5585 | |
|
5586 | 0 | if (metadata) |
5587 | 0 | signaturedata = g_variant_lookup_value (metadata, _OSTREE_METADATA_GPGSIGS_NAME, |
5588 | 0 | _OSTREE_METADATA_GPGSIGS_TYPE); |
5589 | 0 | if (!signaturedata) |
5590 | 0 | { |
5591 | 0 | g_set_error_literal (error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE, |
5592 | 0 | "GPG verification enabled, but no signatures found (use " |
5593 | 0 | "gpg-verify=false in remote config to disable)"); |
5594 | 0 | return NULL; |
5595 | 0 | } |
5596 | | |
5597 | | /* OpenPGP data is organized into binary records called packets. RFC 4880 |
5598 | | * defines a packet as a chunk of data that has a tag specifying its meaning, |
5599 | | * and consists of a packet header followed by a packet body. Each packet |
5600 | | * encodes its own length, and so packets can be concatenated to construct |
5601 | | * OpenPGP messages, keyrings, or in this case, detached signatures. |
5602 | | * |
5603 | | * Each binary blob in the GVariant list is a complete signature packet, so |
5604 | | * we can concatenate them together to verify all the signatures at once. */ |
5605 | 0 | buffer = g_byte_array_new (); |
5606 | 0 | g_variant_iter_init (&iter, signaturedata); |
5607 | 0 | while ((child = g_variant_iter_next_value (&iter)) != NULL) |
5608 | 0 | { |
5609 | 0 | g_byte_array_append (buffer, g_variant_get_data (child), g_variant_get_size (child)); |
5610 | 0 | g_variant_unref (child); |
5611 | 0 | } |
5612 | 0 | signatures = g_byte_array_free_to_bytes (buffer); |
5613 | |
|
5614 | 0 | return _ostree_repo_gpg_verify_data_internal (self, remote_name, signed_data, signatures, |
5615 | 0 | keyringdir, extra_keyring, cancellable, error); |
5616 | 0 | } |
5617 | | |
5618 | | /* Needed an internal version for the remote_name parameter. */ |
5619 | | OstreeGpgVerifyResult * |
5620 | | _ostree_repo_verify_commit_internal (OstreeRepo *self, const char *commit_checksum, |
5621 | | const char *remote_name, GFile *keyringdir, |
5622 | | GFile *extra_keyring, GCancellable *cancellable, |
5623 | | GError **error) |
5624 | 0 | { |
5625 | 0 | g_autoptr (GVariant) commit_variant = NULL; |
5626 | | /* Load the commit */ |
5627 | 0 | if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, commit_checksum, &commit_variant, |
5628 | 0 | error)) |
5629 | 0 | return glnx_prefix_error_null (error, "Failed to read commit"); |
5630 | | |
5631 | | /* Load the metadata */ |
5632 | 0 | g_autoptr (GVariant) metadata = NULL; |
5633 | 0 | if (!ostree_repo_read_commit_detached_metadata (self, commit_checksum, &metadata, cancellable, |
5634 | 0 | error)) |
5635 | 0 | return glnx_prefix_error_null (error, "Failed to read detached metadata"); |
5636 | | |
5637 | 0 | g_autoptr (GBytes) signed_data = g_variant_get_data_as_bytes (commit_variant); |
5638 | | |
5639 | | /* XXX This is a hackish way to indicate to use ALL remote-specific |
5640 | | * keyrings in the signature verification. We want this when |
5641 | | * verifying a signed commit that's already been pulled. */ |
5642 | 0 | if (remote_name == NULL) |
5643 | 0 | remote_name = OSTREE_ALL_REMOTES; |
5644 | |
|
5645 | 0 | return _ostree_repo_gpg_verify_with_metadata (self, signed_data, metadata, remote_name, |
5646 | 0 | keyringdir, extra_keyring, cancellable, error); |
5647 | 0 | } |
5648 | | #endif /* OSTREE_DISABLE_GPGME */ |
5649 | | |
5650 | | /** |
5651 | | * ostree_repo_verify_commit: |
5652 | | * @self: Repository |
5653 | | * @commit_checksum: ASCII SHA256 checksum |
5654 | | * @keyringdir: (allow-none): Path to directory GPG keyrings; overrides built-in default if given |
5655 | | * @extra_keyring: (allow-none): Path to additional keyring file (not a directory) |
5656 | | * @cancellable: Cancellable |
5657 | | * @error: Error |
5658 | | * |
5659 | | * Check for a valid GPG signature on commit named by the ASCII |
5660 | | * checksum @commit_checksum. |
5661 | | * |
5662 | | * Returns: %TRUE if there was a GPG signature from a trusted keyring, otherwise %FALSE |
5663 | | */ |
5664 | | gboolean |
5665 | | ostree_repo_verify_commit (OstreeRepo *self, const gchar *commit_checksum, GFile *keyringdir, |
5666 | | GFile *extra_keyring, GCancellable *cancellable, GError **error) |
5667 | 0 | { |
5668 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5669 | 0 | g_autoptr (OstreeGpgVerifyResult) result = NULL; |
5670 | |
|
5671 | 0 | result = ostree_repo_verify_commit_ext (self, commit_checksum, keyringdir, extra_keyring, |
5672 | 0 | cancellable, error); |
5673 | |
|
5674 | 0 | if (!ostree_gpg_verify_result_require_valid_signature (result, error)) |
5675 | 0 | return glnx_prefix_error (error, "Commit %s", commit_checksum); |
5676 | 0 | return TRUE; |
5677 | | #else |
5678 | | /* FIXME: Return false until refactoring */ |
5679 | | return glnx_throw (error, "GPG feature is disabled in a build time"); |
5680 | | #endif /* OSTREE_DISABLE_GPGME */ |
5681 | 0 | } |
5682 | | |
5683 | | /** |
5684 | | * ostree_repo_verify_commit_ext: |
5685 | | * @self: Repository |
5686 | | * @commit_checksum: ASCII SHA256 checksum |
5687 | | * @keyringdir: (allow-none): Path to directory GPG keyrings; overrides built-in default if given |
5688 | | * @extra_keyring: (allow-none): Path to additional keyring file (not a directory) |
5689 | | * @cancellable: Cancellable |
5690 | | * @error: Error |
5691 | | * |
5692 | | * Read GPG signature(s) on the commit named by the ASCII checksum |
5693 | | * @commit_checksum and return detailed results. |
5694 | | * |
5695 | | * Returns: (transfer full): an #OstreeGpgVerifyResult, or %NULL on error |
5696 | | */ |
5697 | | OstreeGpgVerifyResult * |
5698 | | ostree_repo_verify_commit_ext (OstreeRepo *self, const gchar *commit_checksum, GFile *keyringdir, |
5699 | | GFile *extra_keyring, GCancellable *cancellable, GError **error) |
5700 | 0 | { |
5701 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5702 | 0 | return _ostree_repo_verify_commit_internal (self, commit_checksum, NULL, keyringdir, |
5703 | 0 | extra_keyring, cancellable, error); |
5704 | | #else |
5705 | | glnx_throw (error, "GPG feature is disabled in a build time"); |
5706 | | return NULL; |
5707 | | #endif /* OSTREE_DISABLE_GPGME */ |
5708 | 0 | } |
5709 | | |
5710 | | /** |
5711 | | * ostree_repo_verify_commit_for_remote: |
5712 | | * @self: Repository |
5713 | | * @commit_checksum: ASCII SHA256 checksum |
5714 | | * @remote_name: OSTree remote to use for configuration |
5715 | | * @cancellable: Cancellable |
5716 | | * @error: Error |
5717 | | * |
5718 | | * Read GPG signature(s) on the commit named by the ASCII checksum |
5719 | | * @commit_checksum and return detailed results, based on the keyring |
5720 | | * configured for @remote. |
5721 | | * |
5722 | | * Returns: (transfer full): an #OstreeGpgVerifyResult, or %NULL on error |
5723 | | * |
5724 | | * Since: 2016.14 |
5725 | | */ |
5726 | | OstreeGpgVerifyResult * |
5727 | | ostree_repo_verify_commit_for_remote (OstreeRepo *self, const gchar *commit_checksum, |
5728 | | const gchar *remote_name, GCancellable *cancellable, |
5729 | | GError **error) |
5730 | 0 | { |
5731 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5732 | 0 | return _ostree_repo_verify_commit_internal (self, commit_checksum, remote_name, NULL, NULL, |
5733 | 0 | cancellable, error); |
5734 | | #else |
5735 | | glnx_throw (error, "GPG feature is disabled in a build time"); |
5736 | | return NULL; |
5737 | | #endif /* OSTREE_DISABLE_GPGME */ |
5738 | 0 | } |
5739 | | |
5740 | | /** |
5741 | | * ostree_repo_gpg_verify_data: |
5742 | | * @self: Repository |
5743 | | * @remote_name: (nullable): Name of remote |
5744 | | * @data: Data as a #GBytes |
5745 | | * @signatures: Signatures as a #GBytes |
5746 | | * @keyringdir: (nullable): Path to directory GPG keyrings; overrides built-in default if given |
5747 | | * @extra_keyring: (nullable): Path to additional keyring file (not a directory) |
5748 | | * @cancellable: Cancellable |
5749 | | * @error: Error |
5750 | | * |
5751 | | * Verify @signatures for @data using GPG keys in the keyring for |
5752 | | * @remote_name, and return an #OstreeGpgVerifyResult. |
5753 | | * |
5754 | | * The @remote_name parameter can be %NULL. In that case it will do |
5755 | | * the verifications using GPG keys in the keyrings of all remotes. |
5756 | | * |
5757 | | * Returns: (transfer full): an #OstreeGpgVerifyResult, or %NULL on error |
5758 | | * |
5759 | | * Since: 2016.6 |
5760 | | */ |
5761 | | OstreeGpgVerifyResult * |
5762 | | ostree_repo_gpg_verify_data (OstreeRepo *self, const gchar *remote_name, GBytes *data, |
5763 | | GBytes *signatures, GFile *keyringdir, GFile *extra_keyring, |
5764 | | GCancellable *cancellable, GError **error) |
5765 | 0 | { |
5766 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); |
5767 | 0 | g_return_val_if_fail (data != NULL, NULL); |
5768 | 0 | g_return_val_if_fail (signatures != NULL, NULL); |
5769 | | |
5770 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5771 | 0 | return _ostree_repo_gpg_verify_data_internal ( |
5772 | 0 | self, (remote_name != NULL) ? remote_name : OSTREE_ALL_REMOTES, data, signatures, keyringdir, |
5773 | 0 | extra_keyring, cancellable, error); |
5774 | | #else |
5775 | | glnx_throw (error, "GPG feature is disabled in a build time"); |
5776 | | return NULL; |
5777 | | #endif /* OSTREE_DISABLE_GPGME */ |
5778 | 0 | } |
5779 | | |
5780 | | /** |
5781 | | * ostree_repo_verify_summary: |
5782 | | * @self: Repo |
5783 | | * @remote_name: Name of remote |
5784 | | * @summary: Summary data as a #GBytes |
5785 | | * @signatures: Summary signatures as a #GBytes |
5786 | | * @cancellable: Cancellable |
5787 | | * @error: Error |
5788 | | * |
5789 | | * Verify @signatures for @summary data using GPG keys in the keyring for |
5790 | | * @remote_name, and return an #OstreeGpgVerifyResult. |
5791 | | * |
5792 | | * Returns: (transfer full): an #OstreeGpgVerifyResult, or %NULL on error |
5793 | | */ |
5794 | | OstreeGpgVerifyResult * |
5795 | | ostree_repo_verify_summary (OstreeRepo *self, const char *remote_name, GBytes *summary, |
5796 | | GBytes *signatures, GCancellable *cancellable, GError **error) |
5797 | 0 | { |
5798 | 0 | g_autoptr (GVariant) signatures_variant = NULL; |
5799 | |
|
5800 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); |
5801 | 0 | g_return_val_if_fail (remote_name != NULL, NULL); |
5802 | 0 | g_return_val_if_fail (summary != NULL, NULL); |
5803 | 0 | g_return_val_if_fail (signatures != NULL, NULL); |
5804 | | |
5805 | 0 | signatures_variant |
5806 | 0 | = g_variant_new_from_bytes (OSTREE_SUMMARY_SIG_GVARIANT_FORMAT, signatures, FALSE); |
5807 | |
|
5808 | 0 | #ifndef OSTREE_DISABLE_GPGME |
5809 | 0 | return _ostree_repo_gpg_verify_with_metadata (self, summary, signatures_variant, remote_name, |
5810 | 0 | NULL, NULL, cancellable, error); |
5811 | | #else |
5812 | | glnx_throw (error, "GPG feature is disabled in a build time"); |
5813 | | return NULL; |
5814 | | #endif /* OSTREE_DISABLE_GPGME */ |
5815 | 0 | } |
5816 | | |
5817 | | /* Add an entry for a @ref ↦ @checksum mapping to an `a(s(t@ay@a{sv}))` |
5818 | | * @refs_builder to go into a `summary` file. This includes building the |
5819 | | * standard additional metadata keys for the ref. */ |
5820 | | static gboolean |
5821 | | summary_add_ref_entry (OstreeRepo *self, const char *ref, const char *checksum, |
5822 | | GVariantBuilder *refs_builder, GError **error) |
5823 | 0 | { |
5824 | 0 | g_auto (GVariantDict) commit_metadata_builder = OT_VARIANT_BUILDER_INITIALIZER; |
5825 | |
|
5826 | 0 | g_assert (ref); |
5827 | 0 | g_assert (checksum); |
5828 | | |
5829 | 0 | g_autofree char *remotename = NULL; |
5830 | 0 | if (!ostree_parse_refspec (ref, &remotename, NULL, NULL)) |
5831 | 0 | g_assert_not_reached (); |
5832 | | |
5833 | | /* Don't put remote refs in the summary */ |
5834 | 0 | if (remotename != NULL) |
5835 | 0 | return TRUE; |
5836 | | |
5837 | 0 | g_autoptr (GVariant) commit_obj = NULL; |
5838 | 0 | if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, checksum, &commit_obj, error)) |
5839 | 0 | return FALSE; |
5840 | 0 | g_autoptr (GVariant) orig_metadata = g_variant_get_child_value (commit_obj, 0); |
5841 | |
|
5842 | 0 | g_variant_dict_init (&commit_metadata_builder, NULL); |
5843 | | |
5844 | | /* Forward the commit’s timestamp and version if they're valid. */ |
5845 | 0 | guint64 commit_timestamp = ostree_commit_get_timestamp (commit_obj); |
5846 | 0 | g_autoptr (GDateTime) dt = g_date_time_new_from_unix_utc (commit_timestamp); |
5847 | |
|
5848 | 0 | if (dt != NULL) |
5849 | 0 | g_variant_dict_insert_value (&commit_metadata_builder, OSTREE_COMMIT_TIMESTAMP, |
5850 | 0 | g_variant_new_uint64 (GUINT64_TO_BE (commit_timestamp))); |
5851 | |
|
5852 | 0 | const char *version = NULL; |
5853 | 0 | if (g_variant_lookup (orig_metadata, OSTREE_COMMIT_META_KEY_VERSION, "&s", &version)) |
5854 | 0 | g_variant_dict_insert (&commit_metadata_builder, OSTREE_COMMIT_VERSION, "s", version); |
5855 | |
|
5856 | 0 | g_variant_builder_add_value ( |
5857 | 0 | refs_builder, g_variant_new ("(s(t@ay@a{sv}))", ref, (guint64)g_variant_get_size (commit_obj), |
5858 | 0 | ostree_checksum_to_bytes_v (checksum), |
5859 | 0 | g_variant_dict_end (&commit_metadata_builder))); |
5860 | |
|
5861 | 0 | return TRUE; |
5862 | 0 | } |
5863 | | |
5864 | | static gboolean |
5865 | | regenerate_metadata (OstreeRepo *self, gboolean do_metadata_commit, GVariant *additional_metadata, |
5866 | | GVariant *options, GCancellable *cancellable, GError **error) |
5867 | 0 | { |
5868 | 0 | g_autoptr (OstreeRepoAutoLock) lock = NULL; |
5869 | 0 | gboolean no_deltas_in_summary = FALSE; |
5870 | |
|
5871 | 0 | lock = ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_SHARED, cancellable, error); |
5872 | 0 | if (!lock) |
5873 | 0 | return FALSE; |
5874 | | |
5875 | | /* Parse options vardict. */ |
5876 | 0 | g_autofree char **gpg_key_ids = NULL; |
5877 | 0 | const char *gpg_homedir = NULL; |
5878 | 0 | g_autoptr (GVariant) sign_keys = NULL; |
5879 | 0 | const char *sign_type = NULL; |
5880 | 0 | g_autoptr (OstreeSign) sign = NULL; |
5881 | |
|
5882 | 0 | if (options != NULL) |
5883 | 0 | { |
5884 | 0 | if (!g_variant_is_of_type (options, G_VARIANT_TYPE_VARDICT)) |
5885 | 0 | return glnx_throw (error, "Invalid options doesn't match variant type '%s'", |
5886 | 0 | (const char *)G_VARIANT_TYPE_VARDICT); |
5887 | | |
5888 | 0 | (void)g_variant_lookup (options, "gpg-key-ids", "^a&s", &gpg_key_ids); |
5889 | 0 | (void)g_variant_lookup (options, "gpg-homedir", "&s", &gpg_homedir); |
5890 | 0 | sign_keys = g_variant_lookup_value (options, "sign-keys", G_VARIANT_TYPE_ARRAY); |
5891 | 0 | (void)g_variant_lookup (options, "sign-type", "&s", &sign_type); |
5892 | |
|
5893 | 0 | if (sign_keys != NULL) |
5894 | 0 | { |
5895 | 0 | if (sign_type == NULL) |
5896 | 0 | sign_type = OSTREE_SIGN_NAME_ED25519; |
5897 | |
|
5898 | 0 | sign = ostree_sign_get_by_name (sign_type, error); |
5899 | 0 | if (sign == NULL) |
5900 | 0 | return FALSE; |
5901 | 0 | } |
5902 | 0 | } |
5903 | | |
5904 | 0 | const gchar *main_collection_id = ostree_repo_get_collection_id (self); |
5905 | | |
5906 | | /* Write out a new metadata commit for the repository when it has a collection ID. */ |
5907 | 0 | if (do_metadata_commit && main_collection_id != NULL) |
5908 | 0 | { |
5909 | 0 | g_autoptr (OstreeRepoAutoTransaction) txn |
5910 | 0 | = _ostree_repo_auto_transaction_start (self, cancellable, error); |
5911 | 0 | if (!txn) |
5912 | 0 | return FALSE; |
5913 | | |
5914 | | /* Disable automatic summary updating since we're already doing it */ |
5915 | 0 | self->txn.disable_auto_summary = TRUE; |
5916 | |
|
5917 | 0 | g_autofree gchar *new_ostree_metadata_checksum = NULL; |
5918 | 0 | if (!_ostree_repo_transaction_write_repo_metadata ( |
5919 | 0 | self, additional_metadata, &new_ostree_metadata_checksum, cancellable, error)) |
5920 | 0 | return FALSE; |
5921 | | |
5922 | | /* Sign the new commit. */ |
5923 | 0 | if (gpg_key_ids != NULL) |
5924 | 0 | { |
5925 | 0 | for (const char *const *iter = (const char *const *)gpg_key_ids; |
5926 | 0 | iter != NULL && *iter != NULL; iter++) |
5927 | 0 | { |
5928 | 0 | const char *gpg_key_id = *iter; |
5929 | |
|
5930 | 0 | if (!ostree_repo_sign_commit (self, new_ostree_metadata_checksum, gpg_key_id, |
5931 | 0 | gpg_homedir, cancellable, error)) |
5932 | 0 | return FALSE; |
5933 | 0 | } |
5934 | 0 | } |
5935 | | |
5936 | 0 | if (sign_keys != NULL) |
5937 | 0 | { |
5938 | 0 | GVariantIter *iter; |
5939 | 0 | GVariant *key; |
5940 | |
|
5941 | 0 | g_variant_get (sign_keys, "av", &iter); |
5942 | 0 | while (g_variant_iter_loop (iter, "v", &key)) |
5943 | 0 | { |
5944 | 0 | if (!ostree_sign_set_sk (sign, key, error)) |
5945 | 0 | return FALSE; |
5946 | | |
5947 | 0 | if (!ostree_sign_commit (sign, self, new_ostree_metadata_checksum, cancellable, |
5948 | 0 | error)) |
5949 | 0 | return FALSE; |
5950 | 0 | } |
5951 | 0 | } |
5952 | | |
5953 | 0 | if (!_ostree_repo_auto_transaction_commit (txn, NULL, cancellable, error)) |
5954 | 0 | return FALSE; |
5955 | 0 | } |
5956 | | |
5957 | 0 | g_auto (GVariantDict) additional_metadata_builder = OT_VARIANT_BUILDER_INITIALIZER; |
5958 | 0 | g_variant_dict_init (&additional_metadata_builder, additional_metadata); |
5959 | 0 | g_autoptr (GVariantBuilder) refs_builder |
5960 | 0 | = g_variant_builder_new (G_VARIANT_TYPE ("a(s(taya{sv}))")); |
5961 | |
|
5962 | 0 | { |
5963 | 0 | if (main_collection_id == NULL) |
5964 | 0 | { |
5965 | 0 | g_autoptr (GHashTable) refs = NULL; |
5966 | 0 | if (!ostree_repo_list_refs (self, NULL, &refs, cancellable, error)) |
5967 | 0 | return FALSE; |
5968 | | |
5969 | 0 | g_autoptr (GList) ordered_keys = g_hash_table_get_keys (refs); |
5970 | 0 | ordered_keys = g_list_sort (ordered_keys, (GCompareFunc)strcmp); |
5971 | |
|
5972 | 0 | for (GList *iter = ordered_keys; iter; iter = iter->next) |
5973 | 0 | { |
5974 | 0 | const char *ref = iter->data; |
5975 | 0 | const char *commit = g_hash_table_lookup (refs, ref); |
5976 | |
|
5977 | 0 | if (!summary_add_ref_entry (self, ref, commit, refs_builder, error)) |
5978 | 0 | return FALSE; |
5979 | 0 | } |
5980 | 0 | } |
5981 | 0 | } |
5982 | | |
5983 | 0 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "no-deltas-in-summary", FALSE, |
5984 | 0 | &no_deltas_in_summary, error)) |
5985 | 0 | return FALSE; |
5986 | | |
5987 | 0 | if (!no_deltas_in_summary) |
5988 | 0 | { |
5989 | 0 | g_autoptr (GPtrArray) delta_names = NULL; |
5990 | 0 | g_auto (GVariantDict) deltas_builder = OT_VARIANT_BUILDER_INITIALIZER; |
5991 | |
|
5992 | 0 | if (!ostree_repo_list_static_delta_names (self, &delta_names, cancellable, error)) |
5993 | 0 | return FALSE; |
5994 | | |
5995 | 0 | g_variant_dict_init (&deltas_builder, NULL); |
5996 | 0 | for (guint i = 0; i < delta_names->len; i++) |
5997 | 0 | { |
5998 | 0 | g_autofree char *from = NULL; |
5999 | 0 | g_autofree char *to = NULL; |
6000 | 0 | GVariant *digest; |
6001 | |
|
6002 | 0 | if (!_ostree_parse_delta_name (delta_names->pdata[i], &from, &to, error)) |
6003 | 0 | return FALSE; |
6004 | | |
6005 | 0 | digest = _ostree_repo_static_delta_superblock_digest ( |
6006 | 0 | self, (from && from[0]) ? from : NULL, to, cancellable, error); |
6007 | 0 | if (digest == NULL) |
6008 | 0 | return FALSE; |
6009 | | |
6010 | 0 | g_variant_dict_insert_value (&deltas_builder, delta_names->pdata[i], digest); |
6011 | 0 | } |
6012 | | |
6013 | 0 | if (delta_names->len > 0) |
6014 | 0 | g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_STATIC_DELTAS, |
6015 | 0 | g_variant_dict_end (&deltas_builder)); |
6016 | 0 | } |
6017 | | |
6018 | 0 | { |
6019 | 0 | g_variant_dict_insert_value ( |
6020 | 0 | &additional_metadata_builder, OSTREE_SUMMARY_LAST_MODIFIED, |
6021 | 0 | g_variant_new_uint64 (GUINT64_TO_BE (g_get_real_time () / G_USEC_PER_SEC))); |
6022 | 0 | } |
6023 | |
|
6024 | 0 | { |
6025 | 0 | g_autofree char *remote_mode_str = NULL; |
6026 | 0 | if (!ot_keyfile_get_value_with_default (self->config, "core", "mode", "bare", &remote_mode_str, |
6027 | 0 | error)) |
6028 | 0 | return FALSE; |
6029 | 0 | g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_MODE, |
6030 | 0 | g_variant_new_string (remote_mode_str)); |
6031 | 0 | } |
6032 | | |
6033 | 0 | { |
6034 | 0 | gboolean tombstone_commits = FALSE; |
6035 | 0 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "tombstone-commits", FALSE, |
6036 | 0 | &tombstone_commits, error)) |
6037 | 0 | return FALSE; |
6038 | 0 | g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_TOMBSTONE_COMMITS, |
6039 | 0 | g_variant_new_boolean (tombstone_commits)); |
6040 | 0 | } |
6041 | | |
6042 | 0 | g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_INDEXED_DELTAS, |
6043 | 0 | g_variant_new_boolean (TRUE)); |
6044 | | |
6045 | | /* Add refs which have a collection specified, which could be in refs/mirrors, |
6046 | | * refs/heads, and/or refs/remotes. */ |
6047 | 0 | { |
6048 | 0 | g_autoptr (GHashTable) collection_refs = NULL; |
6049 | 0 | if (!ostree_repo_list_collection_refs (self, NULL, &collection_refs, |
6050 | 0 | OSTREE_REPO_LIST_REFS_EXT_NONE, cancellable, error)) |
6051 | 0 | return FALSE; |
6052 | | |
6053 | 0 | gsize collection_map_size = 0; |
6054 | 0 | GHashTableIter iter; |
6055 | 0 | g_autoptr (GHashTable) collection_map = NULL; /* (element-type utf8 GHashTable) */ |
6056 | 0 | g_hash_table_iter_init (&iter, collection_refs); |
6057 | 0 | collection_map |
6058 | 0 | = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_hash_table_unref); |
6059 | |
|
6060 | 0 | const OstreeCollectionRef *c_ref; |
6061 | 0 | const char *checksum; |
6062 | 0 | while (g_hash_table_iter_next (&iter, (gpointer *)&c_ref, (gpointer *)&checksum)) |
6063 | 0 | { |
6064 | 0 | GHashTable *ref_map = g_hash_table_lookup (collection_map, c_ref->collection_id); |
6065 | |
|
6066 | 0 | if (ref_map == NULL) |
6067 | 0 | { |
6068 | 0 | ref_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); |
6069 | 0 | g_hash_table_insert (collection_map, c_ref->collection_id, ref_map); |
6070 | 0 | } |
6071 | |
|
6072 | 0 | g_hash_table_insert (ref_map, c_ref->ref_name, (gpointer)checksum); |
6073 | 0 | } |
6074 | |
|
6075 | 0 | g_autoptr (GVariantBuilder) collection_refs_builder |
6076 | 0 | = g_variant_builder_new (G_VARIANT_TYPE ("a{sa(s(taya{sv}))}")); |
6077 | |
|
6078 | 0 | g_autoptr (GList) ordered_collection_ids = g_hash_table_get_keys (collection_map); |
6079 | 0 | ordered_collection_ids = g_list_sort (ordered_collection_ids, (GCompareFunc)strcmp); |
6080 | |
|
6081 | 0 | for (GList *collection_iter = ordered_collection_ids; collection_iter; |
6082 | 0 | collection_iter = collection_iter->next) |
6083 | 0 | { |
6084 | 0 | const char *collection_id = collection_iter->data; |
6085 | 0 | GHashTable *ref_map = g_hash_table_lookup (collection_map, collection_id); |
6086 | | |
6087 | | /* We put the local repo's collection ID in the main refs map, rather |
6088 | | * than the collection map, for backwards compatibility. */ |
6089 | 0 | gboolean is_main_collection_id |
6090 | 0 | = (main_collection_id != NULL && g_str_equal (collection_id, main_collection_id)); |
6091 | |
|
6092 | 0 | if (!is_main_collection_id) |
6093 | 0 | { |
6094 | 0 | g_variant_builder_open (collection_refs_builder, G_VARIANT_TYPE ("{sa(s(taya{sv}))}")); |
6095 | 0 | g_variant_builder_add (collection_refs_builder, "s", collection_id); |
6096 | 0 | g_variant_builder_open (collection_refs_builder, G_VARIANT_TYPE ("a(s(taya{sv}))")); |
6097 | 0 | } |
6098 | |
|
6099 | 0 | g_autoptr (GList) ordered_refs = g_hash_table_get_keys (ref_map); |
6100 | 0 | ordered_refs = g_list_sort (ordered_refs, (GCompareFunc)strcmp); |
6101 | |
|
6102 | 0 | for (GList *ref_iter = ordered_refs; ref_iter != NULL; ref_iter = ref_iter->next) |
6103 | 0 | { |
6104 | 0 | const char *ref = ref_iter->data; |
6105 | 0 | const char *commit = g_hash_table_lookup (ref_map, ref); |
6106 | 0 | GVariantBuilder *builder |
6107 | 0 | = is_main_collection_id ? refs_builder : collection_refs_builder; |
6108 | |
|
6109 | 0 | if (!summary_add_ref_entry (self, ref, commit, builder, error)) |
6110 | 0 | return FALSE; |
6111 | | |
6112 | 0 | if (!is_main_collection_id) |
6113 | 0 | collection_map_size++; |
6114 | 0 | } |
6115 | | |
6116 | 0 | if (!is_main_collection_id) |
6117 | 0 | { |
6118 | 0 | g_variant_builder_close (collection_refs_builder); /* array */ |
6119 | 0 | g_variant_builder_close (collection_refs_builder); /* dict entry */ |
6120 | 0 | } |
6121 | 0 | } |
6122 | | |
6123 | 0 | if (main_collection_id != NULL) |
6124 | 0 | g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_COLLECTION_ID, |
6125 | 0 | g_variant_new_string (main_collection_id)); |
6126 | 0 | if (collection_map_size > 0) |
6127 | 0 | g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_COLLECTION_MAP, |
6128 | 0 | g_variant_builder_end (collection_refs_builder)); |
6129 | 0 | } |
6130 | | |
6131 | 0 | g_autoptr (GVariant) summary = NULL; |
6132 | 0 | { |
6133 | 0 | g_autoptr (GVariantBuilder) summary_builder |
6134 | 0 | = g_variant_builder_new (OSTREE_SUMMARY_GVARIANT_FORMAT); |
6135 | |
|
6136 | 0 | g_variant_builder_add_value (summary_builder, g_variant_builder_end (refs_builder)); |
6137 | 0 | g_variant_builder_add_value (summary_builder, |
6138 | 0 | g_variant_dict_end (&additional_metadata_builder)); |
6139 | 0 | summary = g_variant_builder_end (summary_builder); |
6140 | 0 | g_variant_ref_sink (summary); |
6141 | 0 | } |
6142 | |
|
6143 | 0 | if (!ostree_repo_static_delta_reindex (self, 0, NULL, cancellable, error)) |
6144 | 0 | return FALSE; |
6145 | | |
6146 | | /* Create the summary and signature in a temporary directory so that |
6147 | | * the summary isn't published without a matching signature. |
6148 | | */ |
6149 | 0 | g_auto (GLnxTmpDir) summary_tmpdir = { |
6150 | 0 | 0, |
6151 | 0 | }; |
6152 | 0 | if (!glnx_mkdtempat (self->tmp_dir_fd, "summary-XXXXXX", 0777, &summary_tmpdir, error)) |
6153 | 0 | return FALSE; |
6154 | 0 | g_debug ("Using summary tmpdir %s", summary_tmpdir.path); |
6155 | |
|
6156 | 0 | if (!_ostree_repo_file_replace_contents (self, summary_tmpdir.fd, "summary", |
6157 | 0 | g_variant_get_data (summary), |
6158 | 0 | g_variant_get_size (summary), cancellable, error)) |
6159 | 0 | return FALSE; |
6160 | | |
6161 | 0 | if (gpg_key_ids != NULL |
6162 | 0 | && !_ostree_repo_add_gpg_signature_summary_at ( |
6163 | 0 | self, summary_tmpdir.fd, (const char **)gpg_key_ids, gpg_homedir, cancellable, error)) |
6164 | 0 | return FALSE; |
6165 | | |
6166 | 0 | if (sign_keys != NULL |
6167 | 0 | && !_ostree_sign_summary_at (sign, self, summary_tmpdir.fd, sign_keys, cancellable, error)) |
6168 | 0 | return FALSE; |
6169 | | |
6170 | | /* If a signature was made, sync the summary times to it. This way an |
6171 | | * HTTP client will consider the files expired at the same time. |
6172 | | */ |
6173 | 0 | if (gpg_key_ids != NULL || sign_keys != NULL) |
6174 | 0 | { |
6175 | 0 | struct stat stbuf; |
6176 | 0 | if (!glnx_fstatat (summary_tmpdir.fd, "summary.sig", &stbuf, AT_SYMLINK_NOFOLLOW, error)) |
6177 | 0 | return glnx_prefix_error (error, "Unable to get summary.sig status"); |
6178 | | |
6179 | 0 | struct timespec ts[2]; |
6180 | 0 | ts[0] = stbuf.st_atim; |
6181 | 0 | ts[1] = stbuf.st_mtim; |
6182 | 0 | if (TEMP_FAILURE_RETRY (utimensat (summary_tmpdir.fd, "summary", ts, AT_SYMLINK_NOFOLLOW)) |
6183 | 0 | != 0) |
6184 | 0 | return glnx_throw_errno_prefix (error, "Unable to change summary timestamps"); |
6185 | 0 | } |
6186 | | |
6187 | | /* Rename them into place */ |
6188 | 0 | if (!glnx_renameat (summary_tmpdir.fd, "summary", self->repo_dir_fd, "summary", error)) |
6189 | 0 | return glnx_prefix_error (error, "Unable to rename summary file: "); |
6190 | | |
6191 | 0 | if (gpg_key_ids != NULL || sign_keys != NULL) |
6192 | 0 | { |
6193 | 0 | if (!glnx_renameat (summary_tmpdir.fd, "summary.sig", self->repo_dir_fd, "summary.sig", |
6194 | 0 | error)) |
6195 | 0 | { |
6196 | | /* Delete an existing signature since it no longer corresponds |
6197 | | * to the published summary. |
6198 | | */ |
6199 | 0 | g_debug ("Deleting existing unmatched summary.sig file"); |
6200 | 0 | (void)ot_ensure_unlinked_at (self->repo_dir_fd, "summary.sig", NULL); |
6201 | |
|
6202 | 0 | return glnx_prefix_error (error, "Unable to rename summary signature file: "); |
6203 | 0 | } |
6204 | 0 | } |
6205 | 0 | else |
6206 | 0 | { |
6207 | 0 | g_debug ("Deleting existing unmatched summary.sig file"); |
6208 | 0 | if (!ot_ensure_unlinked_at (self->repo_dir_fd, "summary.sig", error)) |
6209 | 0 | return glnx_prefix_error (error, "Unable to delete summary signature file: "); |
6210 | 0 | } |
6211 | | |
6212 | 0 | return TRUE; |
6213 | 0 | } |
6214 | | |
6215 | | /** |
6216 | | * ostree_repo_regenerate_summary: |
6217 | | * @self: Repo |
6218 | | * @additional_metadata: (allow-none): A GVariant of type a{sv}, or %NULL |
6219 | | * @cancellable: Cancellable |
6220 | | * @error: Error |
6221 | | * |
6222 | | * An OSTree repository can contain a high level "summary" file that |
6223 | | * describes the available branches and other metadata. |
6224 | | * |
6225 | | * If the timetable for making commits and updating the summary file is fairly |
6226 | | * regular, setting the `ostree.summary.expires` key in @additional_metadata |
6227 | | * will aid clients in working out when to check for updates. |
6228 | | * |
6229 | | * It is regenerated automatically after any ref is |
6230 | | * added, removed, or updated if `core/auto-update-summary` is set. |
6231 | | * |
6232 | | * If the `core/collection-id` key is set in the configuration, it will be |
6233 | | * included as %OSTREE_SUMMARY_COLLECTION_ID in the summary file. Refs that |
6234 | | * have associated collection IDs will be included in the generated summary |
6235 | | * file, listed under the %OSTREE_SUMMARY_COLLECTION_MAP key. Collection IDs |
6236 | | * and refs in %OSTREE_SUMMARY_COLLECTION_MAP are guaranteed to be in |
6237 | | * lexicographic order. |
6238 | | * |
6239 | | * Locking: shared (Prior to 2021.7, this was exclusive) |
6240 | | */ |
6241 | | gboolean |
6242 | | ostree_repo_regenerate_summary (OstreeRepo *self, GVariant *additional_metadata, |
6243 | | GCancellable *cancellable, GError **error) |
6244 | 0 | { |
6245 | 0 | return regenerate_metadata (self, FALSE, additional_metadata, NULL, cancellable, error); |
6246 | 0 | } |
6247 | | |
6248 | | /** |
6249 | | * ostree_repo_regenerate_metadata: |
6250 | | * @self: Repo |
6251 | | * @additional_metadata: (nullable): A GVariant `a{sv}`, or %NULL |
6252 | | * @options: (nullable): A GVariant `a{sv}` with an extensible set of flags |
6253 | | * @cancellable: Cancellable |
6254 | | * @error: Error |
6255 | | * |
6256 | | * Regenerate the OSTree repository metadata used by clients to describe |
6257 | | * available branches and other metadata. |
6258 | | * |
6259 | | * The repository metadata currently consists of the `summary` file. See |
6260 | | * ostree_repo_regenerate_summary() and %OSTREE_SUMMARY_GVARIANT_FORMAT for |
6261 | | * additional details on its contents. |
6262 | | * |
6263 | | * Additionally, if the `core/collection-id` key is set in the configuration, a |
6264 | | * %OSTREE_REPO_METADATA_REF commit will be created. |
6265 | | * |
6266 | | * The following @options are currently defined: |
6267 | | * |
6268 | | * * `gpg-key-ids` (`as`): Array of GPG key IDs to sign the metadata with. |
6269 | | * * `gpg-homedir` (`s`): GPG home directory. |
6270 | | * * `sign-keys` (`av`): Array of keys to sign the metadata with. The key |
6271 | | * type is specific to the sign engine used. |
6272 | | * * `sign-type` (`s`): Sign engine type to use. If not specified, |
6273 | | * %OSTREE_SIGN_NAME_ED25519 is used. |
6274 | | * |
6275 | | * Locking: shared |
6276 | | * |
6277 | | * Since: 2023.1 |
6278 | | */ |
6279 | | gboolean |
6280 | | ostree_repo_regenerate_metadata (OstreeRepo *self, GVariant *additional_metadata, GVariant *options, |
6281 | | GCancellable *cancellable, GError **error) |
6282 | 0 | { |
6283 | 0 | return regenerate_metadata (self, TRUE, additional_metadata, options, cancellable, error); |
6284 | 0 | } |
6285 | | |
6286 | | /* Regenerate the summary if `core/auto-update-summary` is set. We default to FALSE for |
6287 | | * this setting because OSTree supports multiple processes committing to the same repo (but |
6288 | | * different refs) concurrently, and in fact gnome-continuous actually does this. In that |
6289 | | * context it's best to update the summary explicitly once at the end of multiple |
6290 | | * transactions instead of automatically here. `auto-update-summary` only updates |
6291 | | * atomically within a transaction. */ |
6292 | | gboolean |
6293 | | _ostree_repo_maybe_regenerate_summary (OstreeRepo *self, GCancellable *cancellable, GError **error) |
6294 | 0 | { |
6295 | 0 | gboolean auto_update_summary; |
6296 | 0 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "auto-update-summary", FALSE, |
6297 | 0 | &auto_update_summary, error)) |
6298 | 0 | return FALSE; |
6299 | | |
6300 | | /* Deprecated alias for `auto-update-summary`. */ |
6301 | 0 | gboolean commit_update_summary; |
6302 | 0 | if (!ot_keyfile_get_boolean_with_default (self->config, "core", "commit-update-summary", FALSE, |
6303 | 0 | &commit_update_summary, error)) |
6304 | 0 | return FALSE; |
6305 | | |
6306 | 0 | if ((auto_update_summary || commit_update_summary) |
6307 | 0 | && !ostree_repo_regenerate_summary (self, NULL, cancellable, error)) |
6308 | 0 | return FALSE; |
6309 | | |
6310 | 0 | return TRUE; |
6311 | 0 | } |
6312 | | |
6313 | | gboolean |
6314 | | _ostree_repo_has_staging_prefix (const char *filename) |
6315 | 0 | { |
6316 | 0 | return g_str_has_prefix (filename, OSTREE_REPO_TMPDIR_STAGING); |
6317 | 0 | } |
6318 | | |
6319 | | gboolean |
6320 | | _ostree_repo_try_lock_tmpdir (int tmpdir_dfd, const char *tmpdir_name, GLnxLockFile *file_lock_out, |
6321 | | gboolean *out_did_lock, GError **error) |
6322 | 0 | { |
6323 | 0 | g_autofree char *lock_name = g_strconcat (tmpdir_name, "-lock", NULL); |
6324 | 0 | gboolean did_lock = FALSE; |
6325 | 0 | g_autoptr (GError) local_error = NULL; |
6326 | | |
6327 | | /* We put the lock outside the dir, so we can hold the lock |
6328 | | * until the directory is fully removed */ |
6329 | 0 | if (!glnx_make_lock_file (tmpdir_dfd, lock_name, LOCK_EX | LOCK_NB, file_lock_out, &local_error)) |
6330 | 0 | { |
6331 | | /* we need to handle EACCES too in the case of POSIX locks; see F_SETLK in fcntl(2) */ |
6332 | 0 | if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) |
6333 | 0 | || g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) |
6334 | 0 | { |
6335 | 0 | did_lock = FALSE; |
6336 | 0 | } |
6337 | 0 | else |
6338 | 0 | { |
6339 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
6340 | 0 | return FALSE; |
6341 | 0 | } |
6342 | 0 | } |
6343 | 0 | else |
6344 | 0 | { |
6345 | | /* It's possible that we got a lock after seeing the directory, but |
6346 | | * another process deleted the tmpdir, so verify it still exists. |
6347 | | */ |
6348 | 0 | struct stat stbuf; |
6349 | 0 | if (!glnx_fstatat_allow_noent (tmpdir_dfd, tmpdir_name, &stbuf, AT_SYMLINK_NOFOLLOW, error)) |
6350 | 0 | return FALSE; |
6351 | 0 | if (errno == 0 && S_ISDIR (stbuf.st_mode)) |
6352 | 0 | did_lock = TRUE; |
6353 | 0 | else |
6354 | 0 | glnx_release_lock_file (file_lock_out); |
6355 | 0 | } |
6356 | | |
6357 | 0 | *out_did_lock = did_lock; |
6358 | 0 | return TRUE; |
6359 | 0 | } |
6360 | | |
6361 | | /* This allocates and locks a subdir of the repo tmp dir, using an existing |
6362 | | * one with the same prefix if it is not in use already. */ |
6363 | | gboolean |
6364 | | _ostree_repo_allocate_tmpdir (int tmpdir_dfd, const char *tmpdir_prefix, GLnxTmpDir *tmpdir_out, |
6365 | | GLnxLockFile *file_lock_out, gboolean *reusing_dir_out, |
6366 | | GCancellable *cancellable, GError **error) |
6367 | 0 | { |
6368 | 0 | g_return_val_if_fail (_ostree_repo_has_staging_prefix (tmpdir_prefix), FALSE); |
6369 | | |
6370 | | /* Look for existing tmpdir (with same prefix) to reuse */ |
6371 | 0 | g_auto (GLnxDirFdIterator) dfd_iter = { |
6372 | 0 | 0, |
6373 | 0 | }; |
6374 | 0 | if (!glnx_dirfd_iterator_init_at (tmpdir_dfd, ".", FALSE, &dfd_iter, error)) |
6375 | 0 | return FALSE; |
6376 | | |
6377 | 0 | gboolean reusing_dir = FALSE; |
6378 | 0 | gboolean did_lock = FALSE; |
6379 | 0 | g_auto (GLnxTmpDir) ret_tmpdir = { |
6380 | 0 | 0, |
6381 | 0 | }; |
6382 | 0 | while (!ret_tmpdir.initialized) |
6383 | 0 | { |
6384 | 0 | struct dirent *dent; |
6385 | 0 | g_autoptr (GError) local_error = NULL; |
6386 | |
|
6387 | 0 | if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) |
6388 | 0 | return FALSE; |
6389 | | |
6390 | 0 | if (dent == NULL) |
6391 | 0 | break; |
6392 | | |
6393 | 0 | if (!g_str_has_prefix (dent->d_name, tmpdir_prefix)) |
6394 | 0 | continue; |
6395 | | |
6396 | | /* Quickly skip non-dirs, if unknown we ignore ENOTDIR when opening instead */ |
6397 | 0 | if (dent->d_type != DT_UNKNOWN && dent->d_type != DT_DIR) |
6398 | 0 | continue; |
6399 | | |
6400 | 0 | glnx_autofd int target_dfd = -1; |
6401 | 0 | if (!glnx_opendirat (dfd_iter.fd, dent->d_name, FALSE, &target_dfd, &local_error)) |
6402 | 0 | { |
6403 | 0 | if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY) |
6404 | 0 | || g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) |
6405 | 0 | continue; |
6406 | 0 | else |
6407 | 0 | { |
6408 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
6409 | 0 | return FALSE; |
6410 | 0 | } |
6411 | 0 | } |
6412 | | |
6413 | | /* We put the lock outside the dir, so we can hold the lock |
6414 | | * until the directory is fully removed */ |
6415 | 0 | if (!_ostree_repo_try_lock_tmpdir (tmpdir_dfd, dent->d_name, file_lock_out, &did_lock, error)) |
6416 | 0 | return FALSE; |
6417 | 0 | if (!did_lock) |
6418 | 0 | continue; |
6419 | | |
6420 | | /* Touch the reused directory so that we don't accidentally |
6421 | | * remove it due to being old when cleaning up the tmpdir. |
6422 | | */ |
6423 | 0 | (void)futimens (target_dfd, NULL); |
6424 | | |
6425 | | /* We found an existing tmpdir which we managed to lock */ |
6426 | 0 | g_debug ("Reusing tmpdir %s", dent->d_name); |
6427 | 0 | reusing_dir = TRUE; |
6428 | 0 | ret_tmpdir.src_dfd = tmpdir_dfd; |
6429 | 0 | ret_tmpdir.fd = g_steal_fd (&target_dfd); |
6430 | 0 | ret_tmpdir.path = g_strdup (dent->d_name); |
6431 | 0 | ret_tmpdir.initialized = TRUE; |
6432 | 0 | } |
6433 | | |
6434 | 0 | const char *tmpdir_name_template = glnx_strjoina (tmpdir_prefix, "XXXXXX"); |
6435 | 0 | while (!ret_tmpdir.initialized) |
6436 | 0 | { |
6437 | 0 | g_auto (GLnxTmpDir) new_tmpdir = { |
6438 | 0 | 0, |
6439 | 0 | }; |
6440 | | /* No existing tmpdir found, create a new */ |
6441 | 0 | if (!glnx_mkdtempat (tmpdir_dfd, tmpdir_name_template, DEFAULT_DIRECTORY_MODE, &new_tmpdir, |
6442 | 0 | error)) |
6443 | 0 | return FALSE; |
6444 | | |
6445 | | /* Note, at this point we can race with another process that picks up this |
6446 | | * new directory. If that happens we need to retry, making a new directory. */ |
6447 | 0 | if (!_ostree_repo_try_lock_tmpdir (new_tmpdir.src_dfd, new_tmpdir.path, file_lock_out, |
6448 | 0 | &did_lock, error)) |
6449 | 0 | return FALSE; |
6450 | 0 | if (!did_lock) |
6451 | 0 | { |
6452 | | /* We raced and someone else already locked the newly created |
6453 | | * directory. Free the resources here and then mark it as |
6454 | | * uninitialized so glnx_tmpdir_cleanup doesn't delete the directory |
6455 | | * when new_tmpdir goes out of scope. |
6456 | | */ |
6457 | 0 | glnx_tmpdir_unset (&new_tmpdir); |
6458 | 0 | new_tmpdir.initialized = FALSE; |
6459 | 0 | continue; |
6460 | 0 | } |
6461 | | |
6462 | 0 | g_debug ("Using new tmpdir %s", new_tmpdir.path); |
6463 | 0 | ret_tmpdir = new_tmpdir; /* Transfer ownership */ |
6464 | 0 | new_tmpdir.initialized = FALSE; |
6465 | 0 | } |
6466 | | |
6467 | 0 | *tmpdir_out = ret_tmpdir; /* Transfer ownership */ |
6468 | 0 | ret_tmpdir.initialized = FALSE; |
6469 | 0 | *reusing_dir_out = reusing_dir; |
6470 | 0 | return TRUE; |
6471 | 0 | } |
6472 | | |
6473 | | /* See ostree-repo-private.h for more information about this */ |
6474 | | void |
6475 | | _ostree_repo_memory_cache_ref_init (OstreeRepoMemoryCacheRef *state, OstreeRepo *repo) |
6476 | 0 | { |
6477 | 0 | state->repo = g_object_ref (repo); |
6478 | 0 | GMutex *lock = &repo->cache_lock; |
6479 | 0 | g_mutex_lock (lock); |
6480 | 0 | repo->dirmeta_cache_refcount++; |
6481 | 0 | if (repo->dirmeta_cache == NULL) |
6482 | 0 | repo->dirmeta_cache |
6483 | 0 | = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); |
6484 | 0 | g_mutex_unlock (lock); |
6485 | 0 | } |
6486 | | |
6487 | | /* See ostree-repo-private.h for more information about this */ |
6488 | | void |
6489 | | _ostree_repo_memory_cache_ref_destroy (OstreeRepoMemoryCacheRef *state) |
6490 | 0 | { |
6491 | 0 | OstreeRepo *repo = state->repo; |
6492 | 0 | GMutex *lock = &repo->cache_lock; |
6493 | 0 | g_mutex_lock (lock); |
6494 | 0 | repo->dirmeta_cache_refcount--; |
6495 | 0 | if (repo->dirmeta_cache_refcount == 0) |
6496 | 0 | g_clear_pointer (&repo->dirmeta_cache, g_hash_table_unref); |
6497 | 0 | g_mutex_unlock (lock); |
6498 | 0 | g_object_unref (repo); |
6499 | 0 | } |
6500 | | |
6501 | | /** |
6502 | | * ostree_repo_get_collection_id: |
6503 | | * @self: an #OstreeRepo |
6504 | | * |
6505 | | * Get the collection ID of this repository. See [collection IDs][collection-ids]. |
6506 | | * |
6507 | | * Returns: (nullable): collection ID for the repository |
6508 | | * Since: 2018.6 |
6509 | | */ |
6510 | | const gchar * |
6511 | | ostree_repo_get_collection_id (OstreeRepo *self) |
6512 | 0 | { |
6513 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); |
6514 | | |
6515 | 0 | return self->collection_id; |
6516 | 0 | } |
6517 | | |
6518 | | /** |
6519 | | * ostree_repo_set_collection_id: |
6520 | | * @self: an #OstreeRepo |
6521 | | * @collection_id: (nullable): new collection ID, or %NULL to unset it |
6522 | | * @error: return location for a #GError, or %NULL |
6523 | | * |
6524 | | * Set or clear the collection ID of this repository. See [collection IDs][collection-ids]. |
6525 | | * The update will be made in memory, but must be written out to the repository |
6526 | | * configuration on disk using ostree_repo_write_config(). |
6527 | | * |
6528 | | * Returns: %TRUE on success, %FALSE otherwise |
6529 | | * Since: 2018.6 |
6530 | | */ |
6531 | | gboolean |
6532 | | ostree_repo_set_collection_id (OstreeRepo *self, const gchar *collection_id, GError **error) |
6533 | 0 | { |
6534 | 0 | if (collection_id != NULL && !ostree_validate_collection_id (collection_id, error)) |
6535 | 0 | return FALSE; |
6536 | | |
6537 | 0 | g_autofree gchar *new_collection_id = g_strdup (collection_id); |
6538 | 0 | g_free (self->collection_id); |
6539 | 0 | self->collection_id = g_steal_pointer (&new_collection_id); |
6540 | |
|
6541 | 0 | if (self->config != NULL) |
6542 | 0 | { |
6543 | 0 | if (collection_id != NULL) |
6544 | 0 | g_key_file_set_string (self->config, "core", "collection-id", collection_id); |
6545 | 0 | else |
6546 | 0 | return g_key_file_remove_key (self->config, "core", "collection-id", error); |
6547 | 0 | } |
6548 | | |
6549 | 0 | return TRUE; |
6550 | 0 | } |
6551 | | |
6552 | | /** |
6553 | | * ostree_repo_get_default_repo_finders: |
6554 | | * @self: an #OstreeRepo |
6555 | | * |
6556 | | * Get the set of default repo finders configured. See the documentation for |
6557 | | * the "core.default-repo-finders" config key. |
6558 | | * |
6559 | | * Returns: (array zero-terminated=1) (element-type utf8): |
6560 | | * %NULL-terminated array of strings. |
6561 | | * Since: 2018.9 |
6562 | | */ |
6563 | | const gchar *const * |
6564 | | ostree_repo_get_default_repo_finders (OstreeRepo *self) |
6565 | 0 | { |
6566 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); |
6567 | | |
6568 | 0 | return (const gchar *const *)self->repo_finders; |
6569 | 0 | } |
6570 | | |
6571 | | /** |
6572 | | * ostree_repo_get_bootloader: |
6573 | | * @self: an #OstreeRepo |
6574 | | * |
6575 | | * Get the bootloader configured. See the documentation for the |
6576 | | * "sysroot.bootloader" config key. |
6577 | | * |
6578 | | * Returns: (transfer none): bootloader configuration for the sysroot |
6579 | | * Since: 2019.2 |
6580 | | */ |
6581 | | const gchar * |
6582 | | ostree_repo_get_bootloader (OstreeRepo *self) |
6583 | 0 | { |
6584 | 0 | g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); |
6585 | | |
6586 | 0 | return CFG_SYSROOT_BOOTLOADER_OPTS_STR[self->bootloader]; |
6587 | 0 | } |
6588 | | |
6589 | | /** |
6590 | | * _ostree_repo_verify_bindings: |
6591 | | * @collection_id: (nullable): Locally specified collection ID for the remote |
6592 | | * the @commit was retrieved from, or %NULL if none is configured |
6593 | | * @ref_name: (nullable): Ref name the commit was retrieved using, or %NULL if |
6594 | | * the commit was retrieved by checksum |
6595 | | * @commit: Commit data to check |
6596 | | * @error: Return location for a #GError, or %NULL |
6597 | | * |
6598 | | * Verify the ref and collection bindings. |
6599 | | * |
6600 | | * The ref binding is verified only if it exists. But if we have the |
6601 | | * collection ID specified in the remote configuration (@collection_id is |
6602 | | * non-%NULL) then the ref binding must exist, otherwise the verification will |
6603 | | * fail. Parts of the verification can be skipped by passing %NULL to the |
6604 | | * @ref_name parameter (in case we requested a checksum directly, without |
6605 | | * looking it up from a ref). |
6606 | | * |
6607 | | * The collection binding is verified only when we have collection ID |
6608 | | * specified in the remote configuration. If it is specified, then the |
6609 | | * binding must exist and must be equal to the remote repository |
6610 | | * collection ID. |
6611 | | * |
6612 | | * Returns: %TRUE if bindings are correct, %FALSE otherwise |
6613 | | * Since: 2017.14 |
6614 | | */ |
6615 | | gboolean |
6616 | | _ostree_repo_verify_bindings (const char *collection_id, const char *ref_name, GVariant *commit, |
6617 | | GError **error) |
6618 | 0 | { |
6619 | 0 | g_autoptr (GVariant) metadata = g_variant_get_child_value (commit, 0); |
6620 | 0 | g_autofree const char **refs = NULL; |
6621 | 0 | if (!g_variant_lookup (metadata, OSTREE_COMMIT_META_KEY_REF_BINDING, "^a&s", &refs)) |
6622 | 0 | { |
6623 | | /* Early return here - if the remote collection ID is NULL, then |
6624 | | * we certainly will not verify the collection binding in the |
6625 | | * commit. |
6626 | | */ |
6627 | 0 | if (collection_id == NULL) |
6628 | 0 | return TRUE; |
6629 | | |
6630 | 0 | return glnx_throw (error, "Expected commit metadata to have ref " |
6631 | 0 | "binding information, found none"); |
6632 | 0 | } |
6633 | | |
6634 | 0 | if (ref_name != NULL) |
6635 | 0 | { |
6636 | 0 | if (!g_strv_contains ((const char *const *)refs, ref_name)) |
6637 | 0 | { |
6638 | 0 | g_autoptr (GString) refs_dump = g_string_new (NULL); |
6639 | 0 | const char *refs_str; |
6640 | |
|
6641 | 0 | if (refs != NULL && (*refs) != NULL) |
6642 | 0 | { |
6643 | 0 | for (const char **iter = refs; *iter != NULL; ++iter) |
6644 | 0 | { |
6645 | 0 | const char *ref = *iter; |
6646 | |
|
6647 | 0 | if (refs_dump->len > 0) |
6648 | 0 | g_string_append (refs_dump, ", "); |
6649 | 0 | g_string_append_printf (refs_dump, "‘%s’", ref); |
6650 | 0 | } |
6651 | |
|
6652 | 0 | refs_str = refs_dump->str; |
6653 | 0 | } |
6654 | 0 | else |
6655 | 0 | { |
6656 | 0 | refs_str = "no refs"; |
6657 | 0 | } |
6658 | |
|
6659 | 0 | return glnx_throw (error, |
6660 | 0 | "Commit has no requested ref ‘%s’ " |
6661 | 0 | "in ref binding metadata (%s)", |
6662 | 0 | ref_name, refs_str); |
6663 | 0 | } |
6664 | 0 | } |
6665 | | |
6666 | 0 | if (collection_id != NULL) |
6667 | 0 | { |
6668 | 0 | const char *collection_id_binding; |
6669 | 0 | if (!g_variant_lookup (metadata, OSTREE_COMMIT_META_KEY_COLLECTION_BINDING, "&s", |
6670 | 0 | &collection_id_binding)) |
6671 | 0 | return glnx_throw (error, "Expected commit metadata to have collection ID " |
6672 | 0 | "binding information, found none"); |
6673 | 0 | if (!g_str_equal (collection_id_binding, collection_id)) |
6674 | 0 | return glnx_throw (error, |
6675 | 0 | "Commit has collection ID ‘%s’ in collection binding " |
6676 | 0 | "metadata, while the remote it came from has " |
6677 | 0 | "collection ID ‘%s’", |
6678 | 0 | collection_id_binding, collection_id); |
6679 | 0 | } |
6680 | | |
6681 | 0 | return TRUE; |
6682 | 0 | } |