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
|
#!/bin/sh
# IMPORTANT NOTE: The "local" keyword is not defined in POSIX, so I've decided to work without it.
# For this reason, I do "$X/$f" instead of assigning local variables.
# Another thing to consider is that in recursive functions, backtracking logic
# CANNOT depend on variables, which are changed somewhere in the body before
# the recursive step.
cd "${0%/*}"
[ -z "$HOSTNAME" ] && HOSTNAME="$(hostname)"
if [ -z "$HOSTNAME" ]
then
echo "Set HOSTNAME variable to the system's name!" >/dev/stderr
exit 1
fi
if [ ! -f "./SYSTEM/$HOSTNAME" ]
then
echo "No configuration in ./SYSTEM for '$HOSTNAME'!" >/dev/stderr
exit 1
fi
# __merge_link <.FILES_DIR> <HOME_DIR>
__merge_link() {
while read -r f
do
# Output file doesn't exist -> Create link
# This is also true on broken symlinks
if [ ! -e "$2/$f" ]
then
if [ -h "$2/$f" ]
then
echo "Warn: '$2/$f' is a broken link to '$(readlink "$2/$f")', updating!"
rm "$2/$f"
fi
ln -s "$1/$f" "$2/$f"
# Output file is already linked to current -> Do nothing
elif [ -h "$2/$f" -a "$(readlink $2/$f)" = "$1/$f" ]
then
:
# Current file is a file (and output exists) -> Create link but also update current
elif [ -f "$1/$f" ]
then
if [ -f "$2/$f" ]
then
# When output is NOT a link inside .files,
# update current file with the contents of the output
# Otherwise,
# replace output file with (link to) current
if ! cmp -s "$1/$f" "$2/$f"
then
if [ -h "$2/$f" ]
then
real="$(readlink "$2/$f")"
if [ "${real#$PWD}" = "$real" ]
then
cp "$real" "$1/$f" 2>/dev/null
echo "Warn: '$2/$f' already existed, '$1/$f' updated with its contents!" >/dev/stderr
fi
else
cp "$2/$f" "$1/$f"
echo "Warn: '$2/$f' already existed, '$1/$f' updated with its contents!" >/dev/stderr
fi
fi
rm "$2/$f"
ln -s "$1/$f" "$2/$f"
else
echo "Error: '$2/$f' already exists and is not a regular file!"
fi
# Current file is a directory (and output exists) -> Link current's files & dirs in output directory
elif [ -d "$1/$f" ]
then
if [ -d "$2/$f" ]
then
# If output is a link to directory in .files, continuing with merge
# will place links inside a directory in .files, which is bad.
# Instead, we'll make the link to directory become a directory with
# links to all inner files.
if [ -h "$2/$f" ]
then
real="$(realpath "$2/$f")"
if [ "${real#$PWD}" != "$real" ]
then
rm "$2/$f"
mkdir "$2/$f"
while read -r ff
do
ln -s "$real/$ff" "$2/$f/$ff"
done <<FILES
$(ls -A1 "$real")
FILES
fi
fi
__merge_link "$1/$f" "$2/$f"
else
echo "Error: '$2/$f' already exists and is not a directory!"
fi
else
echo "Error: '$1/$f' is neither a file or directory!"
fi
done <<FILES
$(ls -A1 "$1")
FILES
}
link() {
while read -r dir
do
dir="${dir%%*/}"
if [ ! -d "./$dir" ]
then
echo "Unknown directory '$dir'!" >/dev/stderr
continue
fi
__merge_link "$PWD/$dir" "$HOME"
echo "Linked $dir"
done <"./SYSTEM/$HOSTNAME"
}
# __remove_link <.FILES_DIR> <HOME_DIR>
__remove_link() {
while read -r f
do
if [ -h "$2/$f" ]
then
real="$(realpath "$2/$f")"
# Output file exists and is a link to current -> Remove output
if [ "$real" = "$1/$f" ]
then
rm "$2/$f"
continue
# Output file exists and is a link in .files -> Do nothing (do not remove from .files)
# This is possible in the case when we have .files/X/A and .files/Y/A, and we linked
# .files/X but are now unlinking .files/Y
elif [ "${real#$PWD}" != "$real" ]
then
continue
fi
fi
if [ -d "$1/$f" -a -d "$2/$f" ]
then
__remove_link "$1/$f" "$2/$f"
fi
done <<FILES
$(ls -A1 "$1")
FILES
# See IMPORTANT NOTE, since we're aiming for full POSIX compatibility,
# the variable f will be overwritten by recursive calls, so this loop
# cannot be merged with the loop above (remove dirs on backtracking)
while read -r f
do
# Output file exists and is an empty directory -> Remove it
# We're assuming this directory was created by the script and/or the user
# will not care for empty dirs
if [ -d "$2/$f" -a -z "$(ls -A "$2/$f" 2>/dev/null)" ]
then
rm -r "$2/$f"
fi
done <<FILES
$(ls -A1 "$1")
FILES
}
unlink() {
while read -r dir
do
dir="${dir%%*/}"
if [ ! -d "./$dir" ]
then
echo "Unknown directory '$dir'!" >/dev/stderr
continue
fi
__remove_link "$PWD/$dir" "$HOME"
echo "Unlinked $dir"
done <"./SYSTEM/$HOSTNAME"
}
case "$1" in
(unlink) unlink ;;
(link|*) link ;;
esac
|