-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlib_links.sh
657 lines (588 loc) · 19.3 KB
/
lib_links.sh
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
#!/bin/bash -u
# -----------------------------------------------------------------------------
# LINKS LIBRARY
# -----------------------------------------------------------------------------
#
# ~/config/lib_links.sh
#
# PURPOSE: functions for making links in bash shell.
#
# CONTAINS:
# check_arg1()
# check_arg2()
# check_target()
# do_link()
# do_link_exe()
# do_make_dir()
# do_make_link()
#
# Mar 2024 JCL
#
# -----------------------------------------------------------------------------
# MSYS terminal: to link or not to link
do_junction=false
function check_arg1() {
ddecho -n "$FUNCNAME: number of arguments = $# "
# check number of arguments
if [ $# -lt 1 ]; then
ddecho -e "${BAD}FAIL${RESET}"
echo "$FUNCNAME: One argument is required"
echo "$FUNCNAME: Please provide a target name"
echo "$FUNCNAME: $FUNCNAME TARGET"
return 1
else
ddecho -e "${GOOD}OK${RESET}"
fi
}
function check_arg2() {
# check number of arguments
if [ $# -lt 2 ]; then
echo "number of arguments = $#"
echo "Two arguments are required"
echo "Please provide a target and a link name"
echo "$FUNCNAME TARGET LINK_NAME"
echo "where TARGET is the source file and LINK_NAME is the destination file"
return 1
fi
}
function check_target() {
check_arg1 $@
local funcDEBUG=${DEBUG:-0}
# define target (source)
local target="$@"
local target_canon=$(readlink -f "${target}")
if [ $funcDEBUG -gt 0 ]; then
start_new_line
fecho "input: ${target}"
fecho " path: ${target_canon}"
fi
local -i x_pos
get_curpos x_pos
if [ ${x_pos} -eq 1 ]; then
echo -n "${TAB}"
fi
# determine type
export type="undefined ${BROKEN}"
[ -L "${target}" ] && type="link ${VALID}"
[ -f "${target}" ] && type="file ${FILE}"
[ -d "${target}" ] && type="directory ${DIR}"
# check if target exists
echo -en "target ${type}${target##*/}${RESET}... "
export elin=0
if [ -e "${target_canon}" ]; then
echo -e "${GOOD}exists${RESET}"
return 0
else
echo -e "${BAD}does not exist${RESET}"
return 1
fi
}
# This function is used to check destination link directories that must exist
# like ${HOME} or /etc. If the directory does not exist, return with error.
function check_link_dir() {
check_arg1 $@
local funcDEBUG=${DEBUG:-0}
# define link (destination)
local link="$@"
local link_canon=$(readlink -f "${link}")
if [ $funcDEBUG -gt 0 ]; then
start_new_line
fecho "input: ${target}"
fecho " path: ${target_canon}"
fi
local -i x_pos
get_curpos x_pos
if [ ${x_pos} -eq 1 ]; then
echo -n "${TAB}"
fi
# determine type
export type="undefined ${BROKEN}"
[ -L "${link}" ] && type="link ${VALID}"
[ -f "${link}" ] && type="file ${FILE}"
[ -d "${link}" ] && type="directory ${DIR}"
# check if link exists
echo -en "link ${type}${link}${RESET}... "
if [ -e "${link_canon}" ]; then
echo -e "${GOOD}exists${RESET}"
return 0
else
echo -e "${BAD}does not exist"
return 1
fi
}
function print_OK() {
#echo; return
echo -en "\E[${elin}F\E[0J"
echo -e "${TAB}target ${type}${target##*/}${RESET}... ${GOOD}OK${RESET}"
}
function print_stat() {
if [ $# -lt 1 ]; then
echo -e "${GRAY}UNKNOWN${RESET}"
return 1
fi
local -i RETVAL_in=$1
if [ $RETVAL_in -eq 0 ]; then
echo -e "${GOOD}OK${RESET}"
else
echo -e "${BAD}FAIL${RESET} ${GRAY}RETVAL=$RETVAL_in${RESET}"
fi
}
function print_exclude() {
# return
echo -en "\E[${elin}F\E[0J"
echo -e "${TAB}target ${type}${target}${RESET}... ${GRH}exclude${RESET}"
}
function do_link() {
# PURPOSE - create a link to a target
#
# SYNTAX
# do_link target link_name
# the syntax is the same as the 'ln' intrinsic
# two arguments are required
#
# DEPENDENCIES
# check_target
#
# METHOD -
# FIND check if the target (source) exists
# SSH handling - applies when files are in .ssh/
# check permission of the parent directory
# check the public/private permission of individual files
# exclude linking of certain files
# LINK
# check if link_name exists
# + check if link_name points to target
# + already done, LS link_name and RETURN
# - check if link_name is writable
# + check if link_name and target have the same contents
# + DELETE
# - check if exists
# + get date from date
# - get date from stat
# RENAME the existing file occupying the link_name
# - issue error and RETURN
# - proceed with linking
# LINK
# check if target is authorized_keys
# + create physical link
# - create symbolic link
#
# CALLED BY
# do_link_exe
# check arguments
check_arg2 $@
# define target (source)
local target="$1"
# define link name (destination)
local link_name="$2"
# check if target exists
if [ ${#FUNCNAME[@]} -gt 1 ]; then
if [[ ! ${FUNCNAME[1]} =~ "do_link" ]]; then
check_target "$target" || return 1
else
decho "${TAB}${target##*/} already checked"
fi
fi
# check if target is an SSH configuration file
local target_dir=$(dirname "$target")
if [[ "${target_dir}" == *".ssh" ]]; then
itab
echo "${TAB}Applying options for SSH configuration files..."
((++elin))
# before linking, check parent directory permissions
echo -n "${TAB}${target_dir} requires specific permissions: "
((++elin))
local -i permOK=700
echo "${permOK}"
itab
echo -n "${TAB}checking permissions... "
((++elin))
local -i perm=$(stat -c "%a" "${target_dir}")
echo -n "${perm} "
if [[ ${perm} -gt ${permOK} ]]; then
echo -e "${BAD}FAIL${RESET}"
echo -en "${TAB}${GRH}changing permissions${RESET} to ${permOK}... "
chmod ${permOK} ${target_dir}
print_stat $?
else
echo -e "${GOOD}OK${RESET}"
fi
dtab
# ...and file permissions
if [[ ${target##*/} = id* || ${target##*/} = config || ${target##*/} = authorized_keys* ]]; then
# determine permission requirements
echo -n "${TAB}${target##*/} requires specific permissions: "
((++elin))
if [[ "${target}" == *"pub"* ]]; then
local -i permOK=644
echo -n "PUB "
else
local -i permOK=600
echo -n "PRIV "
fi
echo "${permOK}"
# check existing permissions
itab
echo -n "${TAB}checking permissions... "
((++elin))
# if the target file is itself a link, the permissions are irrelevant; check the
# permissions of the canonicalized target
local target_canon=$(readlink -f "${target}")
local -i perm=$(stat -c "%a" "${target_canon}")
echo -n "${perm} "
((++elin))
# if necessary, the canonicalized target file will have its existing permissions
# replaced with the required permissions
if [[ ${perm} -gt ${permOK} ]]; then
echo -e "${BAD}FAIL${RESET}"
echo -en "${TAB}${GRH}changing permissions${RESET} to ${permOK}... "
chmod ${permOK} ${target_canon}
print_stat $?
else
echo -e "${GOOD}OK${RESET}"
dtab
fi
dtab
fi
# check exclusions
for fname in keys2 pub. \~ id_dsa _[0-9]{4}-[0-9]{2}-[0-9]{2}; do
# check both pattern matching and regex
if [[ ${target##*/} == *"$fname"* ]] || [[ ${target##*/} =~ .*$fname.* ]] ; then
itab
echo -e "${TAB}${GRH}exclude${RESET} $fname: ${target##*/}"
((++elin))
dtab 2
print_exclude
return 0
fi
done
dtab
fi # done with SSH settings
# begin linking...
itab
echo -n "${TAB}link name ${link_name}... "
((++elin))
# first, check for existing copy
if [ -L "${link_name}" ] || [ -f "${link_name}" ] || [ -d "${link_name}" ]; then
itab
echo -n "exists and "
# check if link and target are the same
if [[ "${target}" == "${link_name}" ]]; then
echo -e "${IT}is${NORMAL} ${target}"
echo -n "${TAB}skipping..."
((++elin))
dtab 2 # reset status and link tab
print_OK
return 0
fi
# check if link already points to the target
# in the case of an authorized_keys file, the target must be hard-linked
local inode_target=$(stat -c "%i" "${target}")
local inode_link=$(stat -c "%i" "${link_name}")
if [[ "${target}" -ef "${link_name}" && "${target}" != *"_keys"* ]] || [ ${inode_target} -eq ${inode_link} ] ; then
echo "already points to $(basename ${link_name})"
echo -n "${TAB}"
if [ "$(stat -c "%i" "${target}")" == "$(stat -c "%i" "${link_name}")" ]; then
echo -n "hardlink: "
else
echo -n "symlink: "
fi
#list link name (should point to target)
ls -lhG --color=always "${link_name}" | tr -s ' ' | cut -d' ' -f 8- | cut -c 1-"$(tput cols)"
echo -en ${RESET}
((++elin))
echo -n "${TAB}skipping..."
((++elin))
dtab 2 # reset status and link tab
print_OK
return 0
else
# next, check write permissions
local link_dir=$(dirname "${link_name}")
if [ -w "${link_dir}" ] && ([ -w "${link_name}" ] || [ ! -e "${link_name}" ]); then
# then, delete or backup existing copy
# check file contents
if [ $(diff -ebwB "${target}" "${link_name}" 2>&1 | wc -c) -eq 0 ]; then
echo "has the same contents"
echo -n "${TAB}deleting... "
rm -v "${link_name}"
else
# get file/link modification date
if [ -e "${link_name}" ]; then
echo "will be backed up..."
local mdate=$(date -r "${link_name}" +'%Y-%m-%d-t%H%M')
else
echo "is a broken link..."
local mdate=$(stat -c '%y' "${link_name}" | sed 's/\(^[0-9-]*\) \([0-9:]*\)\..*$/\1-t\2/' | sed 's/://g')
fi
# backup/rename existing file
echo -en "${TAB}"
local link_copy="${link_name}_${mdate}"
mv -v "${link_name}" "${link_copy}"
fi
else
# issue warning
echo -en "${BAD}cannot be written to"
if [ "$EUID" -ne 0 ]; then
echo -e "\n${TAB}${BAD}This command must be run as root!${RESET}"
fi
dtab 2
return 1
fi
#dtab 2
fi
dtab
else
echo "does not exist"
fi
# finally, link target to link_name
itab
echo -en "${GRH}"
hline 72
# check if we're in an MSYS terminal
if [ -z "${MSYSTEM:+dummy}" ]; then
# Non-MSYS prompt: use ln
echo -e "${TAB}${GRH}making link... "
# check if target file is authorized_keys
if [[ "${target}" == *"_keys"* ]]; then
# make a physical link
echo -n "${TAB}PHYS: "
ln -Pv "${target}" ${link_name}
RETVAL=$?
else
# make a symbolic link
echo -n "${TAB}SYM: "
ln -sv "${target}" ${link_name}
RETVAL=$?
fi
else
# MSYS promt: use CMD to create "junction"; otherwise ln just copies the
# target
echo -e "${TAB}${GRH}MSYS terminal: ${MAGENTA}$MSYSTEM${GRH}"
# define directories
wsl_dir=$HOME
decho "${TAB}WSL HOME: $wsl_dir"
cmd_dir=$(cmd.exe /c "echo %systemdrive%%homepath%")
decho "${TAB}CMD HOME: $cmd_dir"
# print link path
decho "${TAB}LINK NAME: ${link_name}"
# replace WSL path with CMD path
ln_path=$(echo ${link_name} | sed "s,^${wsl_dir},," | sed 's,/,\\,g' )
decho "${TAB}PATH: $ln_path"
# define CMD link name
# for some reason the user directory $cmd_dir changes value when envoked
# from sed; must concatonate variables without using sed
cmd_link_name=$(echo ${cmd_dir}${ln_path})
echo "${TAB}link name: $cmd_link_name"
# define CMD target path
decho "${TAB}TARGET : ${target}"
target_path=$(echo ${target} | sed "s,${wsl_dir},," | sed 's,/,\\,g')
cmd_target=$(echo ${cmd_dir}${target_path})
echo "${TAB}target: ${cmd_target}"
# define arguments
args=$(echo ${cmd_link_name} ${cmd_target})
decho "${TAB}ARGS: $args"
# make link in CMD
echo -n "${TAB}JUNCTION: "
cmd.exe /c "mklink /J ${args}"
RETVAL=$?
fi
hline 72
dtab 2
return $RETVAL
}
function do_link_exe() {
# PURPOSE - create a link to an executable file
#
# SYNTAX
# do_link_exe target link_name
# the syntax is the same as the 'ln' intrinsic
# two arguments are required
#
# DEPENDENCIES
# check_target
# do_link
#
# METHOD -
# FIND check if the target (source) exists
# PERM check the target is executable
# LINK pass arguments to do_link
check_arg2 $@
# define target (source)
local target="$1"
# define link (destination)
local link_name="$2"
check_target "$target" || return 1
# next, check file permissions
itab
echo -n "${TAB}${target##*/} requires specific permissions: "
((++elin))
local permOK=500
echo "${permOK}"
itab
echo -n "${TAB}checking permissions... "
((++elin))
local perm=$(stat -c "%a" "${target}")
echo ${perm}
# the target files will have the required permissions added to the existing
# permissions
if [[ ${perm} -le ${permOK} ]] || [[ ! (-f "${target}" && -x "${target}") ]]; then
echo -en "${TAB}${GRH}adding permissions${RESET} to ${permOK}... "
chmod +${permOK} "${target}" || chmod u+rx "${target}"
print_stat $?
else
echo -e "${TAB}permissions ${GOOD}OK${RESET}"
fi
((++elin))
dtab 2
# then link
do_link "$target" "$link_name" || return 1
return 0
}
function check_link() {
# PURPOSE - checks a link to a target
#
# SYNTAX
# check_link target link_name
# the syntax is the same as the 'ln' intrinsic
# two arguments are required
#
# DEPENDENCIES
# check_target
#
# METHOD -
# FIND check if the target (source) exists
# LINK
# check if link_name exists
# + check if link_name points to target
# + already done, LS link_name and RETURN
# - check if link_name is writable
# + check if link_name and target have the same contents
# + DELETE
# - check if exists
# + get date from date
# - get date from stat
# RENAME the existing file occupying the link_name
# - issue error and RETURN
# - proceed with linking
# check arguments
check_arg2 $@
# define target (source)
local target="$1"
# define link name (destination)
local link_name="$2"
check_target "$target" || return 1
check_target "$link_name" || return 1
echo -n "${TAB}${target} and ${link_name}... "
# check if link and target are the same
if [[ "${target}" == "${link_name}" ]]; then
echo -e "$have the same logical name"
fi
# check if link already points to the target
# in the case of an authorized_keys file, the target must be hard-linked
local -i inode_target=$(stat -c "%i" "${target}")
local -i inode_link=$(stat -c "%i" "${link_name}")
decho
decho "target: ${inode_target}"
decho " link: ${inode_link}"
if [ ${inode_target} -eq ${inode_link} ] ; then
decho "same inode"
else
decho "different inode"
fi
if [ ${inode_target} == ${inode_link} ] ; then
echo "are hard-linked"
return 0
else
echo "are not the same"
(
echo "${target}: $inode_target"
echo "${link_name}: $inode_link"
) | column -t -s: -N file,inode | sed "s/^/${TAB}/"
fi
if [[ "${target}" -ef "${link_name}" ]]; then
echo "are symlinked"
echo -n "${TAB}"
ls -lhG --color=always "${link_name}" | tr -s ' ' | cut -d' ' -f 8-
return 0
else
echo "are not linked"
# check file contents
if [ $(diff -ebwB "${target}" "${link_name}" 2>&1 | wc -c) -eq 0 ]; then
echo "have the same contents"
return 0
else
# get file/link modification date
if [ -e "${link_name}" ]; then
echo "has different contents"
return 1
else
echo "is a broken link..."
return 0
fi
fi
fi
dtab
}
function do_make_dir() {
# METHOD
# check if target directory exists
# + return
# - make the directory
#
# DEPENDENCIES
# check_target
local DEBUG=0
check_arg1 $@
# define target (source)
local target="$@"
# Since the specified directory may not exist, a return code of 1 from
# check_target is valid. Disable exit-on-error and turn off traps.
if [[ "$-" == *e* ]]; then
old_opts=$(echo "$-")
set +e
fi
unset_traps
# check if target exists
check_target "$target"
local -i RETVAL=$?
# if target does not exist, make the directory
if [ $RETVAL = 1 ]; then
itab
echo -en "${TAB}${GRH}"
hline 72
echo -e "${TAB}${GRH}making directory... "
echo -n "${TAB}"
mkdir -pv "${target}"
RETVAL=$?
echo -ne "${TAB}"
hline 72
dtab
fi
# reset shell options and traps
reset_shell ${old_opts-''}
reset_traps
# return value of last relevant command
return $RETVAL
}
function do_make_link() {
# DESCRIPTION - check if target directory exists and link to it. If the
# target directory does not exist, create it.
#
# DEPENDENCIES
# check_target
# do_link
# do_make_dir
check_arg2 $@
# define target (source)
local target="$1"
# define link (destination)
local link_name="$2"
do_make_dir "$target" || return 1
do_link "$target" "$link_name" || return 1
return 0
}
function find_broken() {
# find links
find -L ./ -type l | xargs ~/utils/bash/rm_broken_dupes.sh
}