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

CyberShadow / aconfmgr / 662

22 Dec 2025 03:28PM UTC coverage: 91.427% (-2.3%) from 93.708%
662

Pull #232

github

CyberShadow-Renovate
Update dependency aura to v20251216074710
Pull Request #232: Update dependency aura to v20251216074710

0 of 1 new or added line in 1 file covered. (0.0%)

124 existing lines in 16 files now uncovered.

4586 of 5016 relevant lines covered (91.43%)

407.42 hits per line

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

93.47
/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
67✔
7

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

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

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

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

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

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

41
                case "$kind" in
63✔
42
                        mode)
43
                                sudo chmod "$value" "$file"
19✔
44
                                ;;
45
                        owner)
46
                                sudo chown --no-dereference "$value" "$file"
22✔
47
                                ;;
48
                        group)
49
                                sudo chgrp --no-dereference "$value" "$file"
22✔
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"
47✔
63
                local prop
47✔
64

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

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

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

84
                # system
85

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

89
                # Is the target a directory?
90
                if sudo test ! -h "$file" -a -d "$file"
46✔
91
                then
92
                        # Is the source a directory?
93
                        if test ! -h "$source" -a -d "$source"
9✔
94
                        then
95
                                # Update the target's properties in-place.
96
                                target=$file
7✔
97
                        else
98
                                # Delete the existing directory.
99
                                # install/mv can't replace a directory with a non-directory.
UNCOV
100
                                sudo rm --force --dir "$file"
×
101
                        fi
102
                else
103
                        # Is the source a directory?
104
                        if test ! -h "$source" -a -d "$source"
35✔
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"
6✔
109
                        fi
110
                fi
111

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

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

137
                # $system_dir
138

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

145
                mkdir --parents "$(dirname "$system_file")"
94✔
146
                if [[ -h "$source" ]]
47✔
147
                then
148
                        cp --no-dereference "$source" "$system_file"
6✔
149
                elif [[ -d "$source" ]]
41✔
150
                then
151
                        mkdir --parents "$system_file"
15✔
152
                else
153
                        cp --no-dereference "$source" "$system_file"
26✔
154
                fi
155

156
                ApplyFileProps "$file"
47✔
157

158
                if [[ -h "$source" ]]
47✔
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
67✔
169

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

172
        #
173
        # Priority files
174
        #
175

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

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

192
        local file
67✔
193
        comm -12 --zero-terminated \
401✔
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
76✔
197
                do
198
                        LogEnter 'Installing %s...\n' "$(Color C %q "$file")"
18✔
199

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

207
                        InstallFile "$file"
8✔
208
                        LogLeave
9✔
209
                        modified=y
9✔
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
402✔
212
        comm -23 --zero-terminated <(Print0Array changed_files     | sort --zero-terminated) <(Print0Array priority_files | sort --zero-terminated) | mapfile -d $'\0' changed_files
402✔
213

214
        LogLeave # Installing priority files
67✔
215

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

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

226
        #
227
        # Apply packages
228
        #
229

230
        LogEnter 'Configuring packages...\n'
67✔
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
67✔
254
        comm -13                                                                               \
469✔
255
                 <((PrintArray           packages ; PrintArray           foreign_packages) | sort) \
×
256
                 <((PrintArray installed_packages ; PrintArray installed_foreign_packages) | sort) \
×
257
                 | mapfile -t unknown_packages
67✔
258

259
        if [[ ${#unknown_packages[@]} != 0 ]]
67✔
260
        then
261
                LogEnter 'Unpinning %s unknown packages.\n' "$(Color G ${#unknown_packages[@]})"
12✔
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[@]}")" ; }
12✔
265
                Confirm Details
6✔
266

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

269
                modified=y
6✔
270
                LogLeave
6✔
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
67✔
275
        comm -23                                                                                                                                                           \
469✔
276
                 <((PrintArray           packages ; PrintArray           foreign_packages) | sort) \
×
277
                 <((PrintArray installed_packages ; PrintArray installed_foreign_packages) | sort) \
×
278
                 | mapfile -t missing_packages
67✔
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
67✔
283
        comm -12 <(PrintArray missing_packages) <(("$PACMAN" --query --quiet || true) | sort) | mapfile -t missing_unpinned_packages
359✔
284

285
        if [[ ${#missing_unpinned_packages[@]} != 0 ]]
67✔
286
        then
287
                LogEnter 'Pinning %s unknown packages.\n' "$(Color G ${#missing_unpinned_packages[@]})"
10✔
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[@]}")" ; }
10✔
291
                Confirm Details
5✔
292

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

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

299
        # Orphan packages
300

301
        local -a files_in_deleted_packages=()
134✔
302

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

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

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

326
                        if [[ ${#orphan_packages[@]} != 0 ]]
24✔
327
                        then
328
                                LogEnter 'Pruning %s orphan packages.\n' "$(Color G ${#orphan_packages[@]})"
28✔
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[@]}")" ; }
28✔
332
                                ParanoidConfirm Details
14✔
333

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

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

340
                                LogLeave
14✔
341
                        fi
342

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

345
                        LogLeave # Iteration
24✔
346

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

353
                modified=y
10✔
354
                LogLeave # Removing orphan packages
10✔
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
67✔
360
        comm -23 <(PrintArray packages) <(("$PACMAN" --query --quiet || true) | sort) | mapfile -t missing_native_packages
360✔
361

362
        if [[ ${#missing_native_packages[@]} != 0 ]]
67✔
363
        then
364
                LogEnter 'Installing %s missing native packages.\n' "$(Color G ${#missing_native_packages[@]})"
10✔
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[@]}")" ; }
10✔
368
                ParanoidConfirm Details
5✔
369

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

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

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

380
        if [[ ${#missing_foreign_packages[@]} != 0 ]]
67✔
381
        then
382
                LogEnter 'Installing %s missing foreign packages.\n' "$(Color G ${#missing_foreign_packages[@]})"
16✔
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[@]}")" ; }
16✔
386
                Confirm Details
8✔
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
8✔
392
                        for package in "${missing_foreign_packages[@]}"
9✔
393
                        do
394
                                for helper in "${aur_helpers[@]}"
58✔
395
                                do
396
                                        if [[ "$package" == "$helper" ]]
58✔
397
                                        then
398
                                                LogEnter 'Installing AUR helper %s...\n' "$(Color M %q "$helper")"
2✔
399
                                                ParanoidConfirm ''
1✔
400
                                                AconfInstallForeign "$package"
1✔
401
                                                aur_helper="$package"
1✔
402
                                                LogLeave
1✔
403
                                                return
1✔
404
                                        fi
405
                                done
406
                        done
407
                }
408
                if [[ $EUID != 0 ]]
8✔
409
                then
410
                        InstallAurHelper
8✔
411
                fi
412

413
                AconfInstallForeign "${missing_foreign_packages[@]}"
8✔
414

415
                modified=y
8✔
416
                LogLeave
8✔
417
        fi
418

419
        LogLeave # Configuring packages
67✔
420

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

438
        if [[ "${#modified_files_in_deleted_packages[@]}" -gt 0 ]]
67✔
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✔
UNCOV
444
                local file
×
445
                for file in "${modified_files_in_deleted_packages[@]}"
4✔
446
                do
447
                        local system_file="$system_dir"/files"$file"
4✔
448
                        if [[ -h "$system_file" || -f "$system_file" ]]
7✔
449
                        then
450
                                rm --force "$system_file"
2✔
451
                        elif [[ -d "$system_file" ]]
2✔
452
                        then
453
                                rmdir --ignore-fail-on-non-empty "$system_file"
2✔
UNCOV
454
                        elif [[ -e "$system_file" ]]
×
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
7✔
461
                        for prop in "${all_file_property_kinds[@]}"
23✔
462
                        do
463
                                printf '%s\t%s\t%q\n' "$prop" '' "$file" >> "$system_dir"/file-props.txt
23✔
464
                        done
465
                done
466
                LogLeave
1✔
467

468
                LogEnter 'Updating owned file list...\n'
1✔
469
                ( "$PACMAN" --query --list --quiet || true ) | sed 's#\/$##' | sort --unique > "$tmp_dir"/owned-files
2✔
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'
67✔
483

484
        if [[ ${#changed_files[@]} != 0 ]]
66✔
485
        then
486
                LogEnter 'Overwriting %s changed files.\n' "$(Color G ${#changed_files[@]})"
16✔
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[@]}"
27✔
493
                }
494
                Confirm Details
8✔
495

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

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

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

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

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

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

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

535
        if [[ ${#system_only_files[@]} != 0 ]]
67✔
536
        then
537
                LogEnter 'Processing system-only files...\n'
7✔
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'
7✔
542
                local system_only_stray_files=0
7✔
543
                tr '\n' '\0' < "$tmp_dir"/owned-files > "$tmp_dir"/owned-files-0
7✔
544
                comm -13 --zero-terminated "$tmp_dir"/owned-files-0 <(Print0Array system_only_files) | \
14✔
545
                        while read -r -d $'\0' file
19✔
546
                        do
547
                                files_to_delete+=("$file")
12✔
548
                                system_only_stray_files=$((system_only_stray_files+1))
12✔
549
                        done
550
                LogLeave 'Done (%s system-only stray files).\n' "$(Color G %s $system_only_stray_files)"
14✔
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'
7✔
555
                local system_only_owned_files=0
7✔
556
                comm -12 --zero-terminated "$tmp_dir"/owned-files-0 <(Print0Array system_only_files) | \
14✔
557
                        while read -r -d $'\0' file
14✔
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)"
14✔
568

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

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

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

588
        if [[ ${#system_only_file_props[@]} != 0 ]]
67✔
589
        then
590
                local key
4✔
591
                for key in "${system_only_file_props[@]}"
12✔
592
                do
593
                        if [[ "$key" == *:deleted ]]
12✔
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
67✔
613

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

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

627
                local -A parents=()
18✔
628

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

636
                        if [[ -n "${parents[$file]+x}" && -n "$(sudo find "$file" -maxdepth 0 -type d -not -empty 2>/dev/null)" ]]
44✔
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")"
42✔
646
                                ParanoidConfirm ''
21✔
647
                                sudo rm --dir "$file"
21✔
648
                        fi
649

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

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

659
                        LogLeave ''
22✔
660
                done
661

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

666
        if [[ ${#files_to_restore[@]} != 0 ]]
66✔
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
24✔
683
                do
684
                        local package
20✔
685
                        read -r -d $'\0' package
20✔
686
                        file_owners[$file]=$package
20✔
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'
66✔
740

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

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

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

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

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

770
                        if [[ $first == n ]]
15✔
771
                        then
772
                                LogLeave ''
5✔
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"
10✔
780
                        Print0Array changed_file_props     | LogFileProps "Updating"
10✔
781
                        Print0Array system_only_file_props | LogFileProps "Clearing"
10✔
782
                }
783
                Confirm Details
5✔
784

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

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

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

800
        LogLeave # Configuring file properties
65✔
801

802
        LogLeave # Configuring files
63✔
803

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

812
: # include in coverage
134✔
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