forked from rauc/rauc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbundle.c
1944 lines (1690 loc) · 51 KB
/
bundle.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include <errno.h>
#include <gio/gio.h>
#include <gio/gfiledescriptorbased.h>
#include <gio/gunixmounts.h>
#include <glib/gstdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/vfs.h>
#include <openssl/rand.h>
#include "bundle.h"
#include "context.h"
#include "mount.h"
#include "signature.h"
#include "utils.h"
#include "network.h"
#include "dm.h"
#include "verity_hash.h"
/* from statfs(2) man page, as linux/magic.h may not have all of them */
#ifndef AFS_SUPER_MAGIC
#define AFS_SUPER_MAGIC 0x5346414f
#endif
#ifndef BTRFS_SUPER_MAGIC
#define BTRFS_SUPER_MAGIC 0x9123683e
#endif
#ifndef CRAMFS_MAGIC
#define CRAMFS_MAGIC 0x28cd3d45
#endif
#ifndef EXT4_SUPER_MAGIC /* also covers ext2/3 */
#define EXT4_SUPER_MAGIC 0xef53
#endif
#ifndef F2FS_SUPER_MAGIC
#define F2FS_SUPER_MAGIC 0xf2f52010
#endif
#ifndef FUSE_SUPER_MAGIC
#define FUSE_SUPER_MAGIC 0x65735546
#endif
#ifndef HOSTFS_SUPER_MAGIC
#define HOSTFS_SUPER_MAGIC 0x00c0ffee
#endif
#ifndef ISOFS_SUPER_MAGIC
#define ISOFS_SUPER_MAGIC 0x9660
#endif
#ifndef JFFS2_SUPER_MAGIC
#define JFFS2_SUPER_MAGIC 0x72b6
#endif
#ifndef MSDOS_SUPER_MAGIC
#define MSDOS_SUPER_MAGIC 0x4d44
#endif
#ifndef NFS_SUPER_MAGIC
#define NFS_SUPER_MAGIC 0x6969
#endif
#ifndef NTFS_SB_MAGIC
#define NTFS_SB_MAGIC 0x5346544e
#endif
#ifndef OVERLAYFS_SUPER_MAGIC
#define OVERLAYFS_SUPER_MAGIC 0x794c7630
#endif
#ifndef RAMFS_MAGIC
#define RAMFS_MAGIC 0x858458f6
#endif
#ifndef ROMFS_MAGIC
#define ROMFS_MAGIC 0x7275
#endif
#ifndef SQUASHFS_MAGIC
#define SQUASHFS_MAGIC 0x73717368
#endif
#ifndef TMPFS_MAGIC
#define TMPFS_MAGIC 0x01021994
#endif
#ifndef UBIFS_SUPER_MAGIC
#define UBIFS_SUPER_MAGIC 0x24051905
#endif
#ifndef UDF_SUPER_MAGIC
#define UDF_SUPER_MAGIC 0x15013346
#endif
#ifndef XFS_SUPER_MAGIC
#define XFS_SUPER_MAGIC 0x58465342
#endif
#ifndef ZFS_SUPER_MAGIC
/* Taken from https://github.com/openzfs/zfs/blob/master/include/sys/fs/zfs.h#L1198 */
#define ZFS_SUPER_MAGIC 0x2fc12fc1
#endif
#define MAX_BUNDLE_SIGNATURE_SIZE 0x10000
GQuark
r_bundle_error_quark(void)
{
return g_quark_from_static_string("r-bundle-error-quark");
}
static gboolean mksquashfs(const gchar *bundlename, const gchar *contentdir, GError **error)
{
g_autoptr(GSubprocess) sproc = NULL;
GError *ierror = NULL;
gboolean res = FALSE;
g_autoptr(GPtrArray) args = g_ptr_array_new_full(7, g_free);
r_context_begin_step("mksquashfs", "Creating squashfs", 0);
if (g_file_test(bundlename, G_FILE_TEST_EXISTS)) {
g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_EXIST, "bundle %s already exists", bundlename);
goto out;
}
g_ptr_array_add(args, g_strdup("mksquashfs"));
g_ptr_array_add(args, g_strdup(contentdir));
g_ptr_array_add(args, g_strdup(bundlename));
g_ptr_array_add(args, g_strdup("-all-root"));
g_ptr_array_add(args, g_strdup("-noappend"));
g_ptr_array_add(args, g_strdup("-no-progress"));
g_ptr_array_add(args, g_strdup("-no-xattrs"));
if (r_context()->mksquashfs_args != NULL) {
g_auto(GStrv) mksquashfs_argvp = NULL;
res = g_shell_parse_argv(r_context()->mksquashfs_args, NULL, &mksquashfs_argvp, &ierror);
if (!res) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to parse mksquashfs extra args: ");
goto out;
}
for (gchar **mksquashfs_args = mksquashfs_argvp; *mksquashfs_args != NULL; mksquashfs_args++) {
g_ptr_array_add(args, g_strdup(*mksquashfs_args));
}
}
g_ptr_array_add(args, NULL);
sproc = r_subprocess_newv(args, G_SUBPROCESS_FLAGS_STDOUT_SILENCE,
&ierror);
if (sproc == NULL) {
res = FALSE;
g_propagate_prefixed_error(
error,
ierror,
"Failed to start mksquashfs: ");
goto out;
}
res = g_subprocess_wait_check(sproc, NULL, &ierror);
if (!res) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to run mksquashfs: ");
goto out;
}
res = TRUE;
out:
r_context_end_step("mksquashfs", res);
return res;
}
static gboolean unsquashfs(gint fd, const gchar *contentdir, const gchar *extractfile, GError **error)
{
g_autoptr(GSubprocess) sproc = NULL;
GError *ierror = NULL;
gboolean res = FALSE;
g_autoptr(GPtrArray) args = g_ptr_array_new_full(7, g_free);
r_context_begin_step("unsquashfs", "Uncompressing squashfs", 0);
g_ptr_array_add(args, g_strdup("unsquashfs"));
g_ptr_array_add(args, g_strdup("-dest"));
g_ptr_array_add(args, g_strdup(contentdir));
g_ptr_array_add(args, g_strdup_printf("/proc/%jd/fd/%d", (intmax_t)getpid(), fd));
if (extractfile) {
g_ptr_array_add(args, g_strdup(extractfile));
}
g_ptr_array_add(args, NULL);
sproc = r_subprocess_newv(args, G_SUBPROCESS_FLAGS_STDOUT_SILENCE, &ierror);
if (sproc == NULL) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to start unsquashfs: ");
goto out;
}
res = g_subprocess_wait_check(sproc, NULL, &ierror);
if (!res) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to run unsquashfs: ");
goto out;
}
res = TRUE;
out:
r_context_end_step("unsquashfs", res);
return res;
}
static gboolean casync_make_arch(const gchar *idxpath, const gchar *contentpath, const gchar *store, GError **error)
{
g_autoptr(GSubprocess) sproc = NULL;
GError *ierror = NULL;
gboolean res = FALSE;
GPtrArray *args = g_ptr_array_new_full(15, g_free);
GPtrArray *iargs = g_ptr_array_new_full(15, g_free);
const gchar *tmpdir = NULL;
tmpdir = g_dir_make_tmp("arch-XXXXXX", &ierror);
if (tmpdir == NULL) {
g_propagate_prefixed_error(error, ierror,
"Failed to create tmp dir: ");
goto out;
}
/* Inner process call (argument of fakroot sh -c) */
g_ptr_array_add(iargs, g_strdup("tar"));
g_ptr_array_add(iargs, g_strdup("xf"));
g_ptr_array_add(iargs, g_strdup(contentpath));
g_ptr_array_add(iargs, g_strdup("-C"));
g_ptr_array_add(iargs, g_strdup(tmpdir));
g_ptr_array_add(iargs, g_strdup("--numeric-owner"));
g_ptr_array_add(iargs, g_strdup("&&"));
g_ptr_array_add(iargs, g_strdup("casync"));
g_ptr_array_add(iargs, g_strdup("make"));
g_ptr_array_add(iargs, g_strdup("--with=unix"));
g_ptr_array_add(iargs, g_strdup(idxpath));
g_ptr_array_add(iargs, g_strdup(tmpdir));
if (store) {
g_ptr_array_add(iargs, g_strdup("--store"));
g_ptr_array_add(iargs, g_strdup(store));
}
if (r_context()->casync_args != NULL) {
g_auto(GStrv) casync_argvp = NULL;
res = g_shell_parse_argv(r_context()->casync_args, NULL, &casync_argvp, &ierror);
if (!res) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to parse casync extra args: ");
goto out;
}
for (gchar **casync_args = casync_argvp; *casync_args != NULL; casync_args++) {
g_ptr_array_add(iargs, g_strdup(*casync_args));
}
}
g_ptr_array_add(iargs, NULL);
/* Outer process calll */
g_ptr_array_add(args, g_strdup("fakeroot"));
g_ptr_array_add(args, g_strdup("sh"));
g_ptr_array_add(args, g_strdup("-c"));
g_ptr_array_add(args, g_strjoinv(" ", (gchar**) g_ptr_array_free(iargs, FALSE)));
g_ptr_array_add(args, NULL);
sproc = r_subprocess_newv(args, G_SUBPROCESS_FLAGS_STDOUT_SILENCE, &ierror);
if (sproc == NULL) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to start casync: ");
res = FALSE;
goto out;
}
res = g_subprocess_wait_check(sproc, NULL, &ierror);
if (!res) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to run casync: ");
goto out;
}
res = TRUE;
out:
return res;
}
static gboolean casync_make_blob(const gchar *idxpath, const gchar *contentpath, const gchar *store, GError **error)
{
g_autoptr(GSubprocess) sproc = NULL;
GError *ierror = NULL;
gboolean res = FALSE;
GPtrArray *args = g_ptr_array_new_full(5, g_free);
g_ptr_array_add(args, g_strdup("casync"));
g_ptr_array_add(args, g_strdup("make"));
g_ptr_array_add(args, g_strdup(idxpath));
g_ptr_array_add(args, g_strdup(contentpath));
if (store) {
g_ptr_array_add(args, g_strdup("--store"));
g_ptr_array_add(args, g_strdup(store));
}
if (r_context()->casync_args != NULL) {
g_auto(GStrv) casync_argvp = NULL;
res = g_shell_parse_argv(r_context()->casync_args, NULL, &casync_argvp, &ierror);
if (!res) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to parse casync extra args: ");
goto out;
}
for (gchar **casync_args = casync_argvp; *casync_args != NULL; casync_args++) {
g_ptr_array_add(args, g_strdup(*casync_args));
}
}
g_ptr_array_add(args, NULL);
sproc = r_subprocess_newv(args, G_SUBPROCESS_FLAGS_STDOUT_SILENCE, &ierror);
if (sproc == NULL) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to start casync: ");
res = FALSE;
goto out;
}
res = g_subprocess_wait_check(sproc, NULL, &ierror);
if (!res) {
g_propagate_prefixed_error(
error,
ierror,
"Failed to run casync: ");
goto out;
}
res = TRUE;
out:
return res;
}
static gboolean output_stream_write_uint64_all(GOutputStream *stream,
guint64 data,
GCancellable *cancellable,
GError **error)
{
gsize bytes_written;
gboolean res;
data = GUINT64_TO_BE(data);
res = g_output_stream_write_all(stream, &data, sizeof(data), &bytes_written,
cancellable, error);
g_assert(bytes_written == sizeof(data));
return res;
}
static gboolean input_stream_read_uint64_all(GInputStream *stream,
guint64 *data,
GCancellable *cancellable,
GError **error)
{
guint64 tmp;
gsize bytes_read;
gboolean res;
res = g_input_stream_read_all(stream, &tmp, sizeof(tmp), &bytes_read,
cancellable, error);
g_assert(bytes_read == sizeof(tmp));
*data = GUINT64_FROM_BE(tmp);
return res;
}
/* Attempts to read and verify the squashfs magic to verify having a valid bundle */
static gboolean input_stream_check_bundle_identifier(GInputStream *stream, GError **error)
{
GError *ierror = NULL;
guint32 squashfs_id;
gboolean res;
gsize bytes_read;
g_return_val_if_fail(stream, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
res = g_input_stream_read_all(stream, &squashfs_id, sizeof(squashfs_id), &bytes_read, NULL, &ierror);
if (!res) {
g_propagate_error(error, ierror);
return FALSE;
}
if (bytes_read != sizeof(squashfs_id)) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_PARTIAL_INPUT,
"Only %"G_GSIZE_FORMAT " of %zu bytes read",
bytes_read,
sizeof(squashfs_id));
return FALSE;
}
if (squashfs_id != GUINT32_TO_LE(SQUASHFS_MAGIC)) {
g_set_error(error, R_BUNDLE_ERROR, R_BUNDLE_ERROR_IDENTIFIER, "Invalid identifier. Did you pass a valid RAUC bundle?");
return FALSE;
}
return TRUE;
}
static gboolean output_stream_write_bytes_all(GOutputStream *stream,
GBytes *bytes,
GCancellable *cancellable,
GError **error)
{
const void *buffer;
gsize count, bytes_written;
buffer = g_bytes_get_data(bytes, &count);
return g_output_stream_write_all(stream, buffer, count, &bytes_written,
cancellable, error);
}
static gboolean input_stream_read_bytes_all(GInputStream *stream,
GBytes **bytes,
gsize count,
GCancellable *cancellable,
GError **error)
{
g_autofree void *buffer = NULL;
gsize bytes_read;
gboolean res;
g_assert_cmpint(count, !=, 0);
buffer = g_malloc0(count);
res = g_input_stream_read_all(stream, buffer, count, &bytes_read,
cancellable, error);
if (!res) {
return res;
}
g_assert(bytes_read == count);
*bytes = g_bytes_new_take(g_steal_pointer(&buffer), count);
return TRUE;
}
static gboolean sign_bundle(const gchar *bundlename, RaucManifest *manifest, GError **error)
{
GError *ierror = NULL;
g_autoptr(GBytes) sig = NULL;
g_autoptr(GFile) bundlefile = NULL;
g_autoptr(GFileIOStream) bundlestream = NULL;
GOutputStream *bundleoutstream = NULL; /* owned by the bundle stream */
guint64 offset;
g_return_val_if_fail(bundlename, FALSE);
g_return_val_if_fail(manifest, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
g_assert_nonnull(r_context()->certpath);
g_assert_nonnull(r_context()->keypath);
bundlefile = g_file_new_for_path(bundlename);
bundlestream = g_file_open_readwrite(bundlefile, NULL, &ierror);
if (bundlestream == NULL) {
g_propagate_prefixed_error(
error,
ierror,
"failed to open bundle for signing: ");
return FALSE;
}
bundleoutstream = g_io_stream_get_output_stream(G_IO_STREAM(bundlestream));
if (!g_seekable_seek(G_SEEKABLE(bundlestream),
0, G_SEEK_END, NULL, &ierror)) {
g_propagate_prefixed_error(
error,
ierror,
"failed to seek to end of bundle: ");
return FALSE;
}
offset = g_seekable_tell(G_SEEKABLE(bundlestream));
g_debug("Payload size: %" G_GUINT64_FORMAT " bytes.", offset);
if (manifest->bundle_format == R_MANIFEST_FORMAT_PLAIN) {
g_print("Creating bundle in 'plain' format\n");
if (!check_manifest_internal(manifest, &ierror)) {
g_propagate_prefixed_error(
error,
ierror,
"cannot sign bundle containing inconsistent manifest: ");
return FALSE;
}
sig = cms_sign_file(bundlename,
r_context()->certpath,
r_context()->keypath,
r_context()->intermediatepaths,
&ierror);
if (sig == NULL) {
g_propagate_prefixed_error(
error,
ierror,
"failed to sign bundle: ");
return FALSE;
}
} else if (manifest->bundle_format == R_MANIFEST_FORMAT_VERITY) {
int bundlefd = g_file_descriptor_based_get_fd(G_FILE_DESCRIPTOR_BASED(bundleoutstream));
guint8 salt[32] = {0};
guint8 hash[32] = {0};
uint64_t combined_size = 0;
guint64 verity_size = 0;
g_print("Creating bundle in 'verity' format\n");
/* check we have a clean manifest */
g_assert(manifest->bundle_verity_salt == NULL);
g_assert(manifest->bundle_verity_hash == NULL);
g_assert(manifest->bundle_verity_size == 0);
/* dm-verity hash table generation */
if (RAND_bytes((unsigned char *)&salt, sizeof(salt)) != 1) {
g_set_error(error,
R_SIGNATURE_ERROR,
R_SIGNATURE_ERROR_CREATE_SIG,
"failed to generate verity salt");
return FALSE;
}
if (offset % 4096 != 0) {
g_set_error(error,
R_SIGNATURE_ERROR,
R_SIGNATURE_ERROR_CREATE_SIG,
"squashfs size (%"G_GUINT64_FORMAT ") is not a multiple of 4096 bytes", offset);
return FALSE;
}
if (offset <= 4096) {
g_set_error(error,
R_SIGNATURE_ERROR,
R_SIGNATURE_ERROR_CREATE_SIG,
"squashfs size (%"G_GUINT64_FORMAT ") must be larger than 4096 bytes", offset);
return FALSE;
}
if (verity_create_or_verify_hash(0, bundlefd, offset/4096, &combined_size, hash, salt) != 0) {
g_set_error(error,
R_SIGNATURE_ERROR,
R_SIGNATURE_ERROR_CREATE_SIG,
"failed to generate verity hash tree");
return FALSE;
}
/* for a squashfs <= 4096 bytes, we don't have a hash table */
g_assert(combined_size*4096 > (uint64_t)offset);
verity_size = combined_size*4096 - offset;
g_assert(verity_size % 4096 == 0);
manifest->bundle_verity_salt = r_hex_encode(salt, sizeof(salt));
manifest->bundle_verity_hash = r_hex_encode(hash, sizeof(hash));
manifest->bundle_verity_size = verity_size;
if (!check_manifest_external(manifest, &ierror)) {
g_propagate_prefixed_error(
error,
ierror,
"cannot sign inconsistent manifest: ");
return FALSE;
}
sig = cms_sign_manifest(manifest,
r_context()->certpath,
r_context()->keypath,
r_context()->intermediatepaths,
&ierror);
if (sig == NULL) {
g_propagate_prefixed_error(
error,
ierror,
"failed to sign manifest: ");
return FALSE;
}
} else {
g_error("unsupported bundle format");
return FALSE;
}
if (!g_seekable_seek(G_SEEKABLE(bundlestream),
0, G_SEEK_END, NULL, &ierror)) {
g_propagate_prefixed_error(
error,
ierror,
"failed to seek to end of bundle: ");
return FALSE;
}
offset = g_seekable_tell(G_SEEKABLE(bundlestream));
g_debug("Signature offset: %" G_GUINT64_FORMAT " bytes.", offset);
if (!output_stream_write_bytes_all(bundleoutstream, sig, NULL, &ierror)) {
g_propagate_prefixed_error(
error,
ierror,
"failed to append signature to bundle: ");
return FALSE;
}
offset = g_seekable_tell(G_SEEKABLE(bundlestream)) - offset;
if (!output_stream_write_uint64_all(bundleoutstream, offset, NULL, &ierror)) {
g_propagate_prefixed_error(
error,
ierror,
"failed to append signature size to bundle: ");
return FALSE;
}
offset = g_seekable_tell(G_SEEKABLE(bundlestream));
g_debug("Bundle size: %" G_GUINT64_FORMAT " bytes.", offset);
return TRUE;
}
gboolean create_bundle(const gchar *bundlename, const gchar *contentdir, GError **error)
{
GError *ierror = NULL;
g_autofree gchar* manifestpath = g_build_filename(contentdir, "manifest.raucm", NULL);
g_autoptr(RaucManifest) manifest = NULL;
gboolean res = FALSE;
res = load_manifest_file(manifestpath, &manifest, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = sync_manifest_with_contentdir(manifest, contentdir, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = save_manifest_file(manifestpath, manifest, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = mksquashfs(bundlename, contentdir, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = sign_bundle(bundlename, manifest, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = TRUE;
out:
/* Remove output file on error */
if (!res &&
g_file_test(bundlename, G_FILE_TEST_IS_REGULAR) &&
!g_error_matches(ierror, G_FILE_ERROR, G_FILE_ERROR_EXIST))
if (g_remove(bundlename) != 0)
g_warning("failed to remove %s", bundlename);
return res;
}
static gboolean truncate_bundle(const gchar *inpath, const gchar *outpath, goffset size, GError **error)
{
g_autoptr(GFile) infile = NULL;
g_autoptr(GFile) outfile = NULL;
g_autoptr(GFileInputStream) instream = NULL;
g_autoptr(GFileOutputStream) outstream = NULL;
GError *ierror = NULL;
gboolean res = FALSE;
gssize ssize;
if (g_file_test(outpath, G_FILE_TEST_EXISTS)) {
g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_EXIST, "bundle %s already exists", outpath);
res = FALSE;
goto out;
}
infile = g_file_new_for_path(inpath);
outfile = g_file_new_for_path(outpath);
instream = g_file_read(infile, NULL, &ierror);
if (instream == NULL) {
g_propagate_prefixed_error(
error,
ierror,
"failed to open bundle for reading: ");
res = FALSE;
goto out;
}
outstream = g_file_create(outfile, G_FILE_CREATE_NONE, NULL,
&ierror);
if (outstream == NULL) {
g_propagate_prefixed_error(
error,
ierror,
"failed to open bundle for writing: ");
res = FALSE;
goto out;
}
ssize = g_output_stream_splice(
(GOutputStream*)outstream,
(GInputStream*)instream,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
NULL, &ierror);
if (ssize == -1) {
g_propagate_error(error, ierror);
res = FALSE;
goto out;
}
res = g_seekable_truncate(G_SEEKABLE(outstream), size, NULL, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = TRUE;
out:
return res;
}
gboolean resign_bundle(RaucBundle *bundle, const gchar *outpath, GError **error)
{
g_autoptr(RaucManifest) manifest = NULL;
goffset squashfs_size;
GError *ierror = NULL;
gboolean res = FALSE;
g_return_val_if_fail(bundle != NULL, FALSE);
g_return_val_if_fail(outpath != NULL, FALSE);
res = check_bundle_payload(bundle, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = load_manifest_from_bundle(bundle, &manifest, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
if (manifest->bundle_format == R_MANIFEST_FORMAT_PLAIN) {
g_print("Reading bundle in 'plain' format\n");
squashfs_size = bundle->size;
} else if (manifest->bundle_format == R_MANIFEST_FORMAT_VERITY) {
g_print("Reading bundle in 'verity' format\n");
g_assert(bundle->size > (goffset)manifest->bundle_verity_size);
squashfs_size = bundle->size - manifest->bundle_verity_size;
} else {
g_error("unsupported bundle format");
res = FALSE;
goto out;
}
g_clear_pointer(&manifest->bundle_verity_salt, g_free);
g_clear_pointer(&manifest->bundle_verity_hash, g_free);
manifest->bundle_verity_size = 0;
res = truncate_bundle(bundle->path, outpath, squashfs_size, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = sign_bundle(outpath, manifest, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = TRUE;
out:
/* Remove output file on error */
if (!res &&
g_file_test(outpath, G_FILE_TEST_IS_REGULAR) &&
!g_error_matches(ierror, G_FILE_ERROR, G_FILE_ERROR_EXIST))
if (g_remove(outpath) != 0)
g_warning("failed to remove %s", outpath);
return res;
}
static gboolean image_is_archive(RaucImage* image)
{
if (g_pattern_match_simple("*.tar*", image->filename) ||
g_pattern_match_simple("*.catar", image->filename)) {
return TRUE;
}
return FALSE;
}
static gboolean convert_to_casync_bundle(RaucBundle *bundle, const gchar *outbundle, GError **error)
{
GError *ierror = NULL;
gboolean res = FALSE;
g_autofree gchar *tmpdir = NULL;
g_autofree gchar *contentdir = NULL;
g_autofree gchar *mfpath = NULL;
g_autofree gchar *storepath = NULL;
g_autoptr(RaucManifest) manifest = NULL;
g_return_val_if_fail(bundle, FALSE);
g_return_val_if_fail(outbundle, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (g_str_has_suffix(outbundle, ".raucb")) {
g_autofree gchar *basepath = g_strndup(outbundle, strlen(outbundle) - 6);
storepath = g_strconcat(basepath, ".castr", NULL);
} else {
storepath = g_strconcat(outbundle, ".castr", NULL);
}
/* Assure bundle destination path doe not already exist */
if (g_file_test(outbundle, G_FILE_TEST_EXISTS)) {
g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_EXIST, "Destination bundle '%s' already exists", outbundle);
res = FALSE;
goto out;
}
if (g_file_test(storepath, G_FILE_TEST_EXISTS)) {
g_warning("Store path '%s' already exists, appending new chunks", outbundle);
}
/* Set up tmp dir for conversion */
tmpdir = g_dir_make_tmp("rauc-casync-XXXXXX", &ierror);
if (tmpdir == NULL) {
g_propagate_prefixed_error(error, ierror,
"Failed to create tmp dir: ");
res = FALSE;
goto out;
}
contentdir = g_build_filename(tmpdir, "content", NULL);
mfpath = g_build_filename(contentdir, "manifest.raucm", NULL);
/* Extract input bundle to content/ dir */
res = extract_bundle(bundle, contentdir, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
/* Load manifest from content/ dir */
res = load_manifest_file(mfpath, &manifest, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
g_clear_pointer(&manifest->bundle_verity_salt, g_free);
g_clear_pointer(&manifest->bundle_verity_hash, g_free);
manifest->bundle_verity_size = 0;
/* Iterate over each image and convert */
for (GList *l = manifest->images; l != NULL; l = l->next) {
RaucImage *image = l->data;
g_autofree gchar *imgpath = NULL;
g_autofree gchar *idxfile = NULL;
g_autofree gchar *idxpath = NULL;
imgpath = g_build_filename(contentdir, image->filename, NULL);
if (image_is_archive(image)) {
idxfile = g_strconcat(image->filename, ".caidx", NULL);
idxpath = g_build_filename(contentdir, idxfile, NULL);
g_message("Converting %s to directory tree idx %s", image->filename, idxfile);
res = casync_make_arch(idxpath, imgpath, storepath, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
} else {
idxfile = g_strconcat(image->filename, ".caibx", NULL);
idxpath = g_build_filename(contentdir, idxfile, NULL);
g_message("Converting %s to blob idx %s", image->filename, idxfile);
/* Generate index for content */
res = casync_make_blob(idxpath, imgpath, storepath, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
}
/* Rewrite manifest filename */
g_free(image->filename);
image->filename = g_steal_pointer(&idxfile);
/* Remove original file */
if (g_remove(imgpath) != 0) {
g_warning("failed to remove %s", imgpath);
}
}
/* Rewrite manifest to content/ dir */
res = save_manifest_file(mfpath, manifest, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = mksquashfs(outbundle, contentdir, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = sign_bundle(outbundle, manifest, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = TRUE;
out:
/* Remove temporary bundle creation directory */
if (tmpdir)
rm_tree(tmpdir, NULL);
return res;
}
gboolean create_casync_bundle(RaucBundle *bundle, const gchar *outbundle, GError **error)
{
GError *ierror = NULL;
gboolean res = FALSE;
g_return_val_if_fail(bundle != NULL, FALSE);
g_return_val_if_fail(outbundle != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
res = check_bundle_payload(bundle, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = convert_to_casync_bundle(bundle, outbundle, &ierror);
if (!res) {
g_propagate_error(error, ierror);
goto out;
}
res = TRUE;
out:
/* Remove output file on error */
if (!res &&
g_file_test(outbundle, G_FILE_TEST_IS_REGULAR) &&
!g_error_matches(ierror, G_FILE_ERROR, G_FILE_ERROR_EXIST))
if (g_remove(outbundle) != 0)
g_warning("failed to remove %s", outbundle);
return res;
}
static gboolean is_remote_scheme(const gchar *scheme)
{
return (g_strcmp0(scheme, "http") == 0) ||
(g_strcmp0(scheme, "https") == 0) ||
(g_strcmp0(scheme, "sftp") == 0) ||
(g_strcmp0(scheme, "ftp") == 0);
}
static gboolean take_bundle_ownership(int bundle_fd, GError **error)
{
struct stat stat = {};
mode_t perm_orig = 0, perm_new = 0;
gboolean res = FALSE;
if (fstat(bundle_fd, &stat)) {
int err = errno;
g_set_error(error,
G_FILE_ERROR,
g_file_error_from_errno(err),
"failed to fstat bundle: %s", g_strerror(err));
res = FALSE;
goto out;
}
/* if it belongs to someone else, try to fchown */
if ((stat.st_uid != 0) && (stat.st_uid != geteuid())) {
if (fchown(bundle_fd, 0, -1)) {
int err = errno;
g_set_error(error,
G_FILE_ERROR,
g_file_error_from_errno(err),
"failed to chown bundle to root: %s", g_strerror(err));
res = FALSE;
goto out;
}
}
/* allow write permission for user only */
perm_orig = stat.st_mode & 07777;