• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyberShadow / aconfmgr / 661

22 Dec 2025 03:28PM UTC coverage: 79.384% (-14.3%) from 93.708%
661

push

github

web-flow
Merge b1f7495b1 into 8755dc2ab

3223 of 4060 relevant lines covered (79.38%)

293.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.42
/src/apply.bash
1
# apply.bash
2

3
# This file contains the implementation of aconfmgr's 'apply' command.
4

5
function AconfApply() {
6
        local modified=n
41✔
7

8
        function PrintFileProperty() {
9
                local kind="$1"
68✔
10
                local value="$2"
68✔
11
                local file="$3"
68✔
12

13
                local value_text
68✔
14
                if [[ -z "$value" ]]
68✔
15
                then
16
                        local default_value
6✔
17
                        default_value="$(AconfDefaultFileProp "$file" "$kind")"
12✔
18
                        value_text="$(printf '%s (default value)' "$(Color G "%s" "$default_value")")"
18✔
19
                else
20
                        value_text="$(Color G "%s" "$value")"
124✔
21
                fi
22

23
                Log 'Setting %s of %s to %s\n'        \
204✔
24
                        "$(Color Y "%s" "$kind")"        \
204✔
25
                        "$(Color C "%q" "$file")"        \
204✔
26
                        "$value_text"
204✔
27
        }
28

29
        function ApplyFileProperty() {
30
                local kind="$1"
56✔
31
                local value="$2"
56✔
32
                local file="$3"
56✔
33

34
                PrintFileProperty "$kind" "$value" "$file"
56✔
35

36
                if [[ -z "$value" ]]
56✔
37
                then
38
                        value="$(AconfDefaultFileProp "$file" "$kind")"
6✔
39
                fi
40

41
                case "$kind" in
56✔
42
                        mode)
43
                                sudo chmod "$value" "$file"
16✔
44
                                ;;
45
                        owner)
46
                                sudo chown --no-dereference "$value" "$file"
20✔
47
                                ;;
48
                        group)
49
                                sudo chgrp --no-dereference "$value" "$file"
20✔
50
                                ;;
51
                        *)
52
                                Log 'Unknown property %s with value %s for file %s\n' \
×
53
                                        "$(Color Y "%q" "$kind" )" \
×
54
                                        "$(Color G "%q" "$value")" \
×
55
                                        "$(Color C "%q" "$file" )"
×
56
                                Exit 1
×
57
                                ;;
58
                esac
59
        }
60

61
        function ApplyFileProps() {
62
                local file="$1"
38✔
63
                local prop
38✔
64

65
                for prop in "${all_file_property_kinds[@]}"
122✔
66
                do
67
                        local key="$file:$prop"
122✔
68
                        if [[ -n "${output_file_props[$key]+x}" ]]
122✔
69
                        then
70
                                local value="${output_file_props[$key]}"
44✔
71
                                ApplyFileProperty "$prop" "$value" "$file"
44✔
72
                                unset "output_file_props[\$key]"
44✔
73
                                unset "system_file_props[\$key]"
44✔
74

75
                                printf '%s\t%s\t%q\n' "$prop" "$value" "$file" >> "$system_dir"/file-props.txt
44✔
76
                        fi
77
                done
78
        }
79

80
        function InstallFile() {
81
                local file="$1"
37✔
82
                local source="$output_dir"/files/"$file"
37✔
83

84
                # system
85

86
                local target="$file".aconfmgr-new
37✔
87
                sudo rm --force --dir "$target"
36✔
88

89
                # Is the target a directory?
90
                if sudo test ! -h "$file" -a -d "$file"
37✔
91
                then
92
                        # Is the source a directory?
93
                        if test ! -h "$source" -a -d "$source"
6✔
94
                        then
95
                                # Update the target's properties in-place.
96
                                target=$file
4✔
97
                        else
98
                                # Delete the existing directory.
99
                                # install/mv can't replace a directory with a non-directory.
100
                                sudo rm --force --dir "$file"
2✔
101
                        fi
102
                else
103
                        # Is the source a directory?
104
                        if test ! -h "$source" -a -d "$source"
31✔
105
                        then
106
                                # Delete the existing non-directory.
107
                                # install/mv can't replace a non-directory with a directory either.
108
                                sudo rm --force "$file"
2✔
109
                        fi
110
                fi
111

112
                sudo mkdir --parents "$(dirname "$target")"
74✔
113
                if [[ -h "$source" ]]
38✔
114
                then
115
                        sudo cp --no-dereference "$source" "$target"
6✔
116
                        sudo chown --no-dereference root:root "$target"
6✔
117
                elif [[ -d "$source" ]]
32✔
118
                then
119
                        sudo install -d \
11✔
120
                                 --mode="${output_file_props[$file:mode]:-755}" \
11✔
121
                                 --owner="${output_file_props[$file:owner]:-0}" \
11✔
122
                                 --group="${output_file_props[$file:group]:-0}" \
11✔
123
                                 "$target"
11✔
124
                else
125
                        sudo install \
21✔
126
                                 --mode="${output_file_props[$file:mode]:-$default_file_mode}" \
21✔
127
                                 --owner="${output_file_props[$file:owner]:-0}" \
21✔
128
                                 --group="${output_file_props[$file:group]:-0}" \
21✔
129
                                 "$source" "$target"
21✔
130
                fi
131

132
                if [[ "$target" != "$file" ]]
38✔
133
                then
134
                        sudo mv --force --no-target-directory "$target" "$file"
34✔
135
                fi
136

137
                # $system_dir
138

139
                local system_file="$system_dir"/files/"$file"
38✔
140
                if ! { test ! -h "$source" -a -d "$source" && test ! -h "$system_file" -a -d "$system_file" ; }
49✔
141
                then
142
                        rm --force --dir "$system_file"
36✔
143
                fi
144

145
                mkdir --parents "$(dirname "$system_file")"
76✔
146
                if [[ -h "$source" ]]
38✔
147
                then
148
                        cp --no-dereference "$source" "$system_file"
6✔
149
                elif [[ -d "$source" ]]
32✔
150
                then
151
                        mkdir --parents "$system_file"
11✔
152
                else
153
                        cp --no-dereference "$source" "$system_file"
21✔
154
                fi
155

156
                ApplyFileProps "$file"
38✔
157

158
                if [[ -h "$source" ]]
38✔
159
                then
160
                        # ApplyFileProps will apply and unset owner/group. For
161
                        # symlinks, we need to avoid attempting to restore the
162
                        # mode, so unset it here.
163
                        unset "system_file_props[\$file:mode]"
6✔
164
                        printf '%s\t%s\t%q\n' mode '' "$file" >> "$system_dir"/file-props.txt
6✔
165
                fi
166
        }
167

168
        AconfCompile
41✔
169

170
        LogEnter 'Applying configuration...\n'
41✔
171

172
        #
173
        # Priority files
174
        #
175

176
        LogEnter 'Installing priority files...\n'
41✔
177

178
        # shellcheck disable=SC2329  # Callback function invoked indirectly
179
        function Details_DiffFile() {
180
                if sudo test -d "$file"
15✔
181
                then
182
                        Log '%s (old) is a directory.\n' "$(Color C "%q" "$file")"
4✔
183
                elif test -d "$output_dir"/files/"$file"
13✔
184
                then
185
                        Log '%s (new) is a directory.\n' "$(Color C "%q" "$file")"
6✔
186
                else
187
                        AconfNeedProgram diff diffutils n
9✔
188
                        sudo "${diff_opts[@]}" --unified --no-dereference --report-identical-files "$file" "$output_dir"/files/"$file" || true
18✔
189
                fi
190
        }
191

192
        local file
40✔
193
        comm -12 --zero-terminated \
245✔
194
                 <(  Print0Array priority_files                                 | sort --zero-terminated ) \
×
195
                 <( (Print0Array config_only_files ; Print0Array changed_files) | sort --zero-terminated ) | \
×
196
                while read -r -d $'\0' file
47✔
197
                do
198
                        LogEnter 'Installing %s...\n' "$(Color C %q "$file")"
14✔
199

200
                        if sudo test -e "$file"
7✔
201
                        then
202
                                Confirm Details_DiffFile
4✔
203
                        else
204
                                Confirm ''
3✔
205
                        fi
206

207
                        InstallFile "$file"
7✔
208
                        LogLeave
7✔
209
                        modified=y
7✔
210
                done
211
        comm -23 --zero-terminated <(Print0Array config_only_files | sort --zero-terminated) <(Print0Array priority_files | sort --zero-terminated) | mapfile -d $'\0' config_only_files
246✔
212
        comm -23 --zero-terminated <(Print0Array changed_files     | sort --zero-terminated) <(Print0Array priority_files | sort --zero-terminated) | mapfile -d $'\0' changed_files
246✔
213

214
        LogLeave # Installing priority files
41✔
215

216
        if (LC_ALL=C "$PACMAN" 2>&1 || true) | grep --extended-regexp --only-matching 'database file for .+ does not exist'
164✔
217
        then
218
                Log 'The pacman local package database is internally inconsistent. Perform a full refresh and system upgrade now?\n'
×
219
                Confirm ''
×
220

221
                LogEnter 'Performing a full refresh and system upgrade...\n'
×
222
                sudo "$PACMAN" --sync --refresh --sysupgrade --noconfirm
×
223
                LogLeave
×
224
        fi
225

226
        #
227
        # Apply packages
228
        #
229

230
        LogEnter 'Configuring packages...\n'
41✔
231

232
        #        in                in                in-
233
        #        config        system        stalled foreign        action
234
        #        ----------------------------------------
235
        #        no                no                no                no                nothing
236
        #        no                no                no                yes                nothing
237
        #        no                no                yes                no                (prune)
238
        #        no                no                yes                yes                (prune)
239
        #        no                yes                no                no                impossible
240
        #        no                yes                no                yes                impossible
241
        #        no                yes                yes                no                unpin (and prune)
242
        #        no                yes                yes                yes                unpin (and prune)
243
        #        yes                no                no                no                install via pacman
244
        #        yes                no                no                yes                install via makepkg
245
        #        yes                no                yes                no                pin
246
        #        yes                no                yes                yes                pin
247
        #        yes                yes                no                no                impossible
248
        #        yes                yes                no                yes                impossible
249
        #        yes                yes                yes                no                nothing
250
        #        yes                yes                yes                yes                nothing
251

252
        # Unknown packages (native and foreign packages that are explicitly installed but not listed)
253
        local -a unknown_packages
41✔
254
        comm -13                                                                               \
287✔
255
                 <((PrintArray           packages ; PrintArray           foreign_packages) | sort) \
×
256
                 <((PrintArray installed_packages ; PrintArray installed_foreign_packages) | sort) \
×
257
                 | mapfile -t unknown_packages
41✔
258

259
        if [[ ${#unknown_packages[@]} != 0 ]]
41✔
260
        then
261
                LogEnter 'Unpinning %s unknown packages.\n' "$(Color G ${#unknown_packages[@]})"
6✔
262

263
                # shellcheck disable=SC2329  # Callback function invoked indirectly
264
                function Details() { Log 'Unpinning (setting install reason to '\''as dependency'\'') the following packages:%s\n' "$(Color M " %q" "${unknown_packages[@]}")" ; }
6✔
265
                Confirm Details
3✔
266

267
                Print0Array unknown_packages | sudo xargs -0 "$PACMAN" --database --asdeps
6✔
268

269
                modified=y
3✔
270
                LogLeave
3✔
271
        fi
272

273
        # Missing packages (native and foreign packages that are listed in the configuration, but not marked as explicitly installed)
274
        local -a missing_packages
41✔
275
        comm -23                                                                                                                                                           \
287✔
276
                 <((PrintArray           packages ; PrintArray           foreign_packages) | sort) \
×
277
                 <((PrintArray installed_packages ; PrintArray installed_foreign_packages) | sort) \
×
278
                 | mapfile -t missing_packages
41✔
279

280
        # Missing installed/unpinned packages (native and foreign packages that are implicitly installed,
281
        # and listed in the configuration, but not marked as explicitly installed)
282
        local -a missing_unpinned_packages
41✔
283
        comm -12 <(PrintArray missing_packages) <(("$PACMAN" --query --quiet || true) | sort) | mapfile -t missing_unpinned_packages
229✔
284

285
        if [[ ${#missing_unpinned_packages[@]} != 0 ]]
41✔
286
        then
287
                LogEnter 'Pinning %s unknown packages.\n' "$(Color G ${#missing_unpinned_packages[@]})"
4✔
288

289
                # shellcheck disable=SC2329  # Callback function invoked indirectly
290
                function Details() { Log 'Pinning (setting install reason to '\''explicitly installed'\'') the following packages:%s\n' "$(Color M " %q" "${missing_unpinned_packages[@]}")" ; }
4✔
291
                Confirm Details
2✔
292

293
                Print0Array missing_unpinned_packages | sudo xargs -0 "$PACMAN" --database --asexplicit
4✔
294

295
                modified=y
2✔
296
                LogLeave
2✔
297
        fi
298

299
        # Orphan packages
300

301
        local -a files_in_deleted_packages=()
82✔
302

303
        if ( "$PACMAN" --query --unrequired --unrequired --deps --quiet --native || true ) |
79✔
304
                   grep -qvFxf <(PrintArray ignore_packages        ) > /dev/null ||
82✔
305
           ( "$PACMAN" --query --unrequired --unrequired --deps --quiet --foreign || true ) |
72✔
306
                   grep -qvFxf <(PrintArray ignore_foreign_packages) > /dev/null
72✔
307
        then
308
                LogEnter 'Pruning orphan packages...\n'
5✔
309

310
                # We have to loop, since pacman's dependency scanning doesn't seem to be recursive
311
                local iter=1
5✔
312
                while true
10✔
313
                do
314
                        LogEnter 'Iteration %s:\n' "$(Color G "$iter")"
20✔
315

316
                        LogEnter 'Querying orphan packages...\n'
10✔
317
                        local -a orphan_packages
10✔
318
                        {
319
                                ( "$PACMAN" --query --unrequired --unrequired --deps --quiet --native || true ) |
13✔
320
                                        ( grep -vFxf <(PrintArray ignore_packages        ) || true )
25✔
321
                                ( "$PACMAN" --query --unrequired --unrequired --deps --quiet --foreign || true ) |
20✔
322
                                        ( grep -vFxf <(PrintArray ignore_foreign_packages) || true )
30✔
323
                        } | mapfile -t orphan_packages
10✔
324
                        LogLeave
10✔
325

326
                        if [[ ${#orphan_packages[@]} != 0 ]]
10✔
327
                        then
328
                                LogEnter 'Pruning %s orphan packages.\n' "$(Color G ${#orphan_packages[@]})"
10✔
329

330
                                # shellcheck disable=SC2329  # Callback function invoked indirectly
331
                                function Details() { Log 'Removing the following orphan packages:%s\n' "$(Color M " %q" "${orphan_packages[@]}")" ; }
10✔
332
                                ParanoidConfirm Details
5✔
333

334
                                local -a deleted_files=()
10✔
335
                                "$PACMAN" --query --list --quiet "${orphan_packages[@]}" | sed 's#^\(.*\)/$#\1#' | mapfile -t deleted_files
15✔
336
                                files_in_deleted_packages+=("${deleted_files[@]}")
5✔
337

338
                                sudo "${pacman_opts[@]}" --remove "${orphan_packages[@]}"
5✔
339

340
                                LogLeave
5✔
341
                        fi
342

343
                        iter=$((iter+1))
10✔
344

345
                        LogLeave # Iteration
10✔
346

347
                        if [[ ${#orphan_packages[@]} == 0 ]]
10✔
348
                        then
349
                                break
5✔
350
                        fi
351
                done
352

353
                modified=y
5✔
354
                LogLeave # Removing orphan packages
5✔
355
        fi
356

357

358
        # Missing native packages (native packages that are listed in the configuration, but not installed)
359
        local -a missing_native_packages
41✔
360
        comm -23 <(PrintArray packages) <(("$PACMAN" --query --quiet || true) | sort) | mapfile -t missing_native_packages
230✔
361

362
        if [[ ${#missing_native_packages[@]} != 0 ]]
41✔
363
        then
364
                LogEnter 'Installing %s missing native packages.\n' "$(Color G ${#missing_native_packages[@]})"
6✔
365

366
                # shellcheck disable=SC2329  # Callback function invoked indirectly
367
                function Details() { Log 'Installing the following native packages:%s\n' "$(Color M " %q" "${missing_native_packages[@]}")" ; }
6✔
368
                ParanoidConfirm Details
3✔
369

370
                AconfInstallNative "${missing_native_packages[@]}"
3✔
371

372
                modified=y
3✔
373
                LogLeave
3✔
374
        fi
375

376
        # Missing foreign packages (foreign packages that are listed in the configuration, but not installed)
377
        local -a missing_foreign_packages
41✔
378
        comm -23 <(PrintArray foreign_packages) <(("$PACMAN" --query --quiet || true) | sort) | mapfile -t missing_foreign_packages
228✔
379

380
        if [[ ${#missing_foreign_packages[@]} != 0 ]]
41✔
381
        then
382
                LogEnter 'Installing %s missing foreign packages.\n' "$(Color G ${#missing_foreign_packages[@]})"
×
383

384
                # shellcheck disable=SC2329  # Callback function invoked indirectly
385
                function Details() { Log 'Installing the following foreign packages:%s\n' "$(Color M " %q" "${missing_foreign_packages[@]}")" ; }
386
                Confirm Details
×
387

388
                # If an AUR helper is present in the list of packages to be installed,
389
                # install it first, then use it to install the rest of the foreign packages.
390
                function InstallAurHelper() {
391
                        local package helper
×
392
                        for package in "${missing_foreign_packages[@]}"
×
393
                        do
394
                                for helper in "${aur_helpers[@]}"
×
395
                                do
396
                                        if [[ "$package" == "$helper" ]]
×
397
                                        then
398
                                                LogEnter 'Installing AUR helper %s...\n' "$(Color M %q "$helper")"
×
399
                                                ParanoidConfirm ''
×
400
                                                AconfInstallForeign "$package"
×
401
                                                aur_helper="$package"
×
402
                                                LogLeave
×
403
                                                return
×
404
                                        fi
405
                                done
406
                        done
407
                }
408
                if [[ $EUID != 0 ]]
×
409
                then
410
                        InstallAurHelper
×
411
                fi
412

413
                AconfInstallForeign "${missing_foreign_packages[@]}"
×
414

415
                modified=y
×
416
                LogLeave
×
417
        fi
418

419
        LogLeave # Configuring packages
41✔
420

421
        # Read file owners
422
        local -a modified_files_in_deleted_packages
41✔
423
        (
424
                cat "$tmp_dir"/output-files "$tmp_dir"/system-files
41✔
425
                local kind value file
41✔
426
                cat "$output_dir"/file-props.txt "$system_dir"/file-props.txt \
41✔
427
                        | \
×
428
                        while IFS=$'\t' read -r kind value file
439✔
429
                        do
430
                                eval "printf '%s\\0' $file" # Unescape
356✔
431
                        done
432
        ) \
×
433
                | sort --zero-terminated --unique \
41✔
434
                | comm --zero-terminated -12 <(Print0Array files_in_deleted_packages | sort --zero-terminated --unique) /dev/stdin \
123✔
435
                | tac -s $'\0' \
41✔
436
                | mapfile -t -d $'\0' modified_files_in_deleted_packages
41✔
437

438
        if [[ "${#modified_files_in_deleted_packages[@]}" -gt 0 ]]
41✔
439
        then
440
                LogEnter 'Detected %s modified files in pruned packages.\n' \
2✔
441
                                 "$(Color G %s "${#modified_files_in_deleted_packages[@]}")"
2✔
442

443
                LogEnter 'Updating system file data...\n'
1✔
444
                local file
1✔
445
                for file in "${modified_files_in_deleted_packages[@]}"
7✔
446
                do
447
                        local system_file="$system_dir"/files"$file"
7✔
448
                        if [[ -h "$system_file" || -f "$system_file" ]]
10✔
449
                        then
450
                                rm --force "$system_file"
2✔
451
                        elif [[ -d "$system_file" ]]
3✔
452
                        then
453
                                rmdir --ignore-fail-on-non-empty "$system_file"
2✔
454
                        elif [[ -e "$system_file" ]]
1✔
455
                        then
456
                                FatalError '%s exists, but is neither file or directory or link?\n' \
×
457
                                                   "$(Color C "%q" "$file")"
×
458
                        fi
459

460
                        local prop
5✔
461
                        for prop in "${all_file_property_kinds[@]}"
21✔
462
                        do
463
                                printf '%s\t%s\t%q\n' "$prop" '' "$file" >> "$system_dir"/file-props.txt
22✔
464
                        done
465
                done
466
                LogLeave
×
467

468
                LogEnter 'Updating owned file list...\n'
×
469
                ( "$PACMAN" --query --list --quiet || true ) | sed 's#\/$##' | sort --unique > "$tmp_dir"/owned-files
3✔
470
                LogLeave
1✔
471

472
                LogEnter 'Rescanning...\n'
1✔
473
                AconfAnalyzeFiles
1✔
474
                LogLeave
1✔
475
                LogLeave
1✔
476
        fi
477

478
        #
479
        # Copy files
480
        #
481

482
        LogEnter 'Configuring files...\n'
41✔
483

484
        if [[ ${#changed_files[@]} != 0 ]]
40✔
485
        then
486
                LogEnter 'Overwriting %s changed files.\n' "$(Color G ${#changed_files[@]})"
15✔
487

488
                # shellcheck disable=2059
489
                # shellcheck disable=SC2329  # Callback function invoked indirectly
490
                function Details() {
491
                        Log 'Overwriting the following changed files:\n'
8✔
492
                        printf "$(Color W "*") $(Color C "%s" "%s")\\n" "${changed_files[@]}"
23✔
493
                }
494
                Confirm Details
8✔
495

496
                for file in "${changed_files[@]}"
12✔
497
                do
498
                        LogEnter 'Overwriting %s...\n' "$(Color C "%q" "$file")"
24✔
499
                        ParanoidConfirm Details_DiffFile
11✔
500
                        InstallFile "$file"
11✔
501
                        LogLeave ''
12✔
502
                done
503

504
                modified=y
8✔
505
                LogLeave
8✔
506
        fi
507

508
        if [[ ${#config_only_files[@]} != 0 ]]
41✔
509
        then
510
                LogEnter 'Installing %s new files.\n' "$(Color G ${#config_only_files[@]})"
20✔
511

512
                # shellcheck disable=2059
513
                # shellcheck disable=SC2329  # Callback function invoked indirectly
514
                function Details() {
515
                        Log 'Installing the following new files:\n'
10✔
516
                        printf "$(Color W "*") $(Color C "%s" "%s")\\n" "${config_only_files[@]}"
30✔
517
                }
518
                Confirm Details
10✔
519

520
                for file in "${config_only_files[@]}"
19✔
521
                do
522
                        LogEnter 'Installing %s...\n' "$(Color C "%q" "$file")"
38✔
523
                        ParanoidConfirm ''
19✔
524
                        InstallFile "$file"
19✔
525
                        LogLeave ''
19✔
526
                done
527

528
                modified=y
10✔
529
                LogLeave
10✔
530
        fi
531

532
        local -a files_to_delete=()
82✔
533
        local -a files_to_restore=()
82✔
534

535
        if [[ ${#system_only_files[@]} != 0 ]]
41✔
536
        then
537
                LogEnter 'Processing system-only files...\n'
6✔
538

539
                # Delete unknown stray files (files not present in config and belonging to no package)
540

541
                LogEnter 'Filtering system-only stray files...\n'
6✔
542
                local system_only_stray_files=0
6✔
543
                tr '\n' '\0' < "$tmp_dir"/owned-files > "$tmp_dir"/owned-files-0
6✔
544
                comm -13 --zero-terminated "$tmp_dir"/owned-files-0 <(Print0Array system_only_files) | \
12✔
545
                        while read -r -d $'\0' file
15✔
546
                        do
547
                                files_to_delete+=("$file")
9✔
548
                                system_only_stray_files=$((system_only_stray_files+1))
9✔
549
                        done
550
                LogLeave 'Done (%s system-only stray files).\n' "$(Color G %s $system_only_stray_files)"
12✔
551

552
                # Restore unknown owned files (files not present in config and belonging to a package)
553

554
                LogEnter 'Filtering system-only owned files...\n'
6✔
555
                local system_only_owned_files=0
6✔
556
                comm -12 --zero-terminated "$tmp_dir"/owned-files-0 <(Print0Array system_only_files) | \
12✔
557
                        while read -r -d $'\0' file
13✔
558
                        do
559
                                if [[ "${output_file_props[$file:deleted]:-}" == y ]]
7✔
560
                                then
561
                                        continue # Don't restore files that the user wants deleted
1✔
562
                                fi
563

564
                                files_to_restore+=("$file")
6✔
565
                                system_only_owned_files=$((system_only_owned_files+1))
6✔
566
                        done
567
                LogLeave 'Done (%s system-only owned files).\n' "$(Color G %s $system_only_owned_files)"
12✔
568

569
                LogLeave # Processing system-only files
6✔
570
        fi
571

572
        LogEnter 'Processing deleted files...\n'
41✔
573

574
        if [[ ${#config_only_file_props[@]} != 0 ]]
41✔
575
        then
576
                local key
6✔
577
                for key in "${config_only_file_props[@]}"
33✔
578
                do
579
                        if [[ "$key" == *:deleted ]]
33✔
580
                        then
581
                                local file="${key%:*}"
6✔
582
                                files_to_delete+=("$file")
6✔
583
                                unset "output_file_props[\$key]"
6✔
584
                        fi
585
                done
586
        fi
587

588
        if [[ ${#system_only_file_props[@]} != 0 ]]
41✔
589
        then
590
                local key
3✔
591
                for key in "${system_only_file_props[@]}"
9✔
592
                do
593
                        if [[ "$key" == *:deleted ]]
9✔
594
                        then
595
                                local file="${key%:*}"
3✔
596

597
                                if [[ -h "$output_dir"/files/"$file" || -e "$output_dir"/files/"$file" ]]
5✔
598
                                then
599
                                        # If we are going to replace a deleted file with
600
                                        # one from the configuration, do not attempt to
601
                                        # restore it.
602
                                        :
2✔
603
                                else
604
                                        files_to_restore+=("$file")
1✔
605
                                fi
606

607
                                unset "system_file_props[\$key]"
3✔
608
                        fi
609
                done
610
        fi
611

612
        LogLeave # Processing deleted files
41✔
613

614
        if [[ ${#files_to_delete[@]} != 0 ]]
41✔
615
        then
616
                LogEnter 'Deleting %s files.\n' "$(Color G ${#files_to_delete[@]})"
12✔
617
                printf '%s\0' "${files_to_delete[@]}" | sort --zero-terminated | mapfile -d $'\0' files_to_delete
18✔
618

619
                # shellcheck disable=2059
620
                # shellcheck disable=SC2329  # Callback function invoked indirectly
621
                function Details() {
622
                        Log 'Deleting the following files:\n'
6✔
623
                        printf "$(Color W "*") $(Color C "%s" "%s")\\n" "${files_to_delete[@]}"
18✔
624
                }
625
                Confirm Details
6✔
626

627
                local -A parents=()
12✔
628

629
                # Iterate backwards, so that inner files/directories are
630
                # deleted before their parent ones.
631
                local i
6✔
632
                for (( i=${#files_to_delete[@]}-1 ; i >= 0 ; i-- ))
42✔
633
                do
634
                        local file="${files_to_delete[$i]}"
15✔
635

636
                        if [[ -n "${parents[$file]+x}" && -n "$(sudo find "$file" -maxdepth 0 -type d -not -empty 2>/dev/null)" ]]
29✔
637
                        then
638
                                # Ignoring paths under a directory can cause us to
639
                                # want to remove a directory which will in fact not be
640
                                # empty, and actually contain ignored files. So, skip
641
                                # deleting empty directories which are parents of
642
                                # previously-deleted objects.
643
                                LogEnter 'Skipping non-empty directory %s.\n' "$(Color C "%q" "$file")"
2✔
644
                        else
645
                                LogEnter 'Deleting %s...\n' "$(Color C "%q" "$file")"
28✔
646
                                ParanoidConfirm ''
14✔
647
                                sudo rm --dir "$file"
14✔
648
                        fi
649

650
                        local prop
15✔
651
                        for prop in "${all_file_property_kinds[@]}"
20✔
652
                        do
653
                                local key="$file:$prop"
20✔
654
                                unset "system_file_props[\$key]"
20✔
655
                        done
656

657
                        parents["$(dirname "$file")"]=y
30✔
658

659
                        LogLeave ''
15✔
660
                done
661

662
                modified=y
6✔
663
                LogLeave
6✔
664
        fi
665

666
        if [[ ${#files_to_restore[@]} != 0 ]]
41✔
667
        then
668
                LogEnter 'Restoring %s files.\n' "$(Color G ${#files_to_restore[@]})"
6✔
669
                printf '%s\0' "${files_to_restore[@]}" | sort --zero-terminated | mapfile -d $'\0' files_to_restore
9✔
670

671
                # shellcheck disable=2059
672
                # shellcheck disable=SC2329  # Callback function invoked indirectly
673
                function Details() {
674
                        Log 'Restoring the following files:\n'
3✔
675
                        printf "$(Color W "*") $(Color C "%s" "%s")\\n" "${files_to_restore[@]}"
9✔
676
                }
677
                Confirm Details
3✔
678

679
                # Read file owners
680
                local -A file_owners
3✔
681
                local file
3✔
682
                while read -r -d $'\0' file
34✔
683
                do
684
                        local package
31✔
685
                        read -r -d $'\0' package
31✔
686
                        file_owners[$file]=$package
32✔
687
                done < "$tmp_dir"/file-owners
×
688

689
                for file in "${files_to_restore[@]}"
7✔
690
                do
691
                        local package
7✔
692

693
                        if [[ -n "${file_owners[$file]+x}" ]]
7✔
694
                        then
695
                                package=${file_owners[$file]}
6✔
696
                        else
697
                                package="$( ("$PACMAN" --query --owns --quiet "$file" || true) | head -n 1)"
3✔
698

699
                                if [[ -z "$package" ]]
1✔
700
                                then
701
                                        Log 'Can'\''t find package owning file %s\n' "$(Color C "%q" "$file")"
×
702
                                        Exit 1
×
703
                                fi
704
                        fi
705

706
                        LogEnter 'Restoring %s file %s...\n' "$(Color M "%q" "$package")" "$(Color C "%q" "$file")"
21✔
707
                        # shellcheck disable=SC2329  # Callback function invoked indirectly
708
                        function Details() {
709
                                AconfNeedProgram diff diffutils n
1✔
710
                                AconfGetPackageOriginalFile "$package" "$file" | ( "${diff_opts[@]}" --unified <(SuperCat "$file") - || true )
4✔
711
                        }
712
                        if sudo test -f "$file"
6✔
713
                        then
714
                                ParanoidConfirm Details
1✔
715
                        else
716
                                ParanoidConfirm ''
6✔
717
                        fi
718

719
                        AconfRestoreFile "$package" "$file"
7✔
720

721
                        # The file was restored with all of its original properties.
722
                        local prop
7✔
723
                        for prop in owner group mode
21✔
724
                        do
725
                                unset "system_file_props[\$file:\$prop]"
21✔
726
                        done
727

728
                        LogLeave ''
7✔
729
                done
730

731
                modified=y
3✔
732
                LogLeave
3✔
733
        fi
734

735
        #
736
        # Apply remaining file properties
737
        #
738

739
        LogEnter 'Configuring file properties...\n'
41✔
740

741
        AconfCompareFileProps # Update data after ApplyFileProps' unsets
41✔
742

743
        if [[ ${#config_only_file_props[@]} != 0 || ${#changed_file_props[@]} != 0 || ${#system_only_file_props[@]} != 0 ]]
116✔
744
        then
745
                LogEnter 'Found %s new, %s changed, and %s extra files properties.\n'        \
12✔
746
                                 "$(Color G ${#config_only_file_props[@]})"                                                \
12✔
747
                                 "$(Color G ${#changed_file_props[@]})"                                                 \
12✔
748
                                 "$(Color G ${#system_only_file_props[@]})"
12✔
749

12✔
750
                # shellcheck disable=SC2329  # Callback function invoked indirectly
751
                function LogFileProps() {
752
                        local verb="$1"
9✔
753
                        local first=y
9✔
754
                        local key
9✔
755

756
                        while read -r -d $'\0' key
21✔
757
                        do
758
                                if [[ $first == y ]]
12✔
759
                                then
760
                                        LogEnter '%s the following file properties:\n' "$verb"
3✔
761
                                        first=n
3✔
762
                                fi
763

764
                                local kind="${key##*:}"
12✔
765
                                local file="${key%:*}"
12✔
766
                                local value="${output_file_props[$key]:-}"
12✔
767
                                PrintFileProperty "$kind" "$value" "$file"
12✔
768
                        done
769

770
                        if [[ $first == n ]]
9✔
771
                        then
772
                                LogLeave ''
3✔
773
                        fi
774
                }
775

776
                # shellcheck disable=2059
777
                # shellcheck disable=SC2329  # Callback function invoked indirectly
778
                function Details() {
779
                        Print0Array config_only_file_props | LogFileProps "Setting"
6✔
780
                        Print0Array changed_file_props     | LogFileProps "Updating"
6✔
781
                        Print0Array system_only_file_props | LogFileProps "Clearing"
6✔
782
                }
783
                Confirm Details
3✔
784

785
                local key
3✔
786
                ( Print0Array config_only_file_props ; Print0Array changed_file_props ; Print0Array system_only_file_props ) | \
9✔
787
                        while read -r -d $'\0' key
15✔
788
                        do
789
                                local kind="${key##*:}"
12✔
790
                                local file="${key%:*}"
12✔
791
                                local value="${output_file_props[$key]:-}"
12✔
792

793
                                ApplyFileProperty "$kind" "$value" "$file"
12✔
794
                        done
795

796
                modified=y
3✔
797
                LogLeave
3✔
798
        fi
799

800
        LogLeave # Configuring file properties
40✔
801

802
        LogLeave # Configuring files
39✔
803

804
        if [[ $modified == n ]]
39✔
805
        then
806
                LogLeave 'Done (%s).\n' "$(Color G "system state unchanged")"
16✔
807
        else
808
                LogLeave 'Done (%s).\n' "$(Color Y "system state changed")"
66✔
809
        fi
810
}
811

812
: # include in coverage
90✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc