]> git.wh0rd.org Git - home.git/blob - .bin/backup-dvd
vunshare: avoid SIGUNUSED
[home.git] / .bin / backup-dvd
1 #!/bin/bash
2
3 err() { printf 'error: %b\n' "$*" 1>&2; exit 1; }
4 usage() {
5         cat <<-EOF
6         backup-dvd [options] [commands]
7
8         Options:
9          -i <input dev>      Defaults to ${dev}
10          -o <output dir>     Defaults to ${out}
11          -n <volume name>    Defaults to volume on disk
12
13         Commands:
14          --errors   Image the raw disc first w/ddrescue
15          --backup   Back up the disc
16          --modify   Tweak runtime settings (needs -n)
17          --mkiso    Create an iso (needs -n)
18
19         If no commands are specified, then all are run.
20         EOF
21         exit ${1:-0}
22 }
23
24 dev=/dev/cdrom
25 out=${PWD}
26 vol=
27
28 unset doit_{backup,modify,mkiso}
29 doit_errors=false
30
31 eval set -- `getopt -l errors,backup,modify,mkiso -- hi:n:o: "$@"`
32 while [[ -n $1 ]] ; do
33         case $1 in
34                 -h) usage;;
35                 -i) dev=$2; shift 2;;
36                 -o) out=$2; shift 2;;
37                 -n) vol=$2; shift 2;;
38
39                 --errors) doit_errors=true; shift;;
40                 --backup) doit_backup=true; shift;;
41                 --modify) doit_modify=true; shift;;
42                 --mkiso)  doit_mkiso=true;  shift;;
43
44                 --) shift; break;;
45                 -*) usage 1;;
46                 *)  usage 2; break;;
47         esac
48 done
49
50 doit_all() {
51         : ${doit_backup:=$1}
52         : ${doit_modify:=$1}
53         : ${doit_mkiso:=$1}
54 }
55 if [[ -z ${doit_backup}${doit_modify}${doit_mkiso} ]] ; then
56         doit_all true
57 else
58         doit_all false
59 fi
60
61 # iso-info              mkisofs
62 Application=    # -A
63 Preparer=               # -p
64 Publisher=              # -publisher
65 System=                 # -sysid
66 Volume=${vol}   # -V
67 Volume_Set=             # -volset
68
69 if ${doit_backup} ; then
70         if [[ -z ${Volume} ]] ; then
71                 info=$(iso-info ${dev}) || exit 1
72
73                 eval $(echo "${info}" | awk -F: '
74                         (NF > 1 && $1 !~ /image/) {
75                                 sub(/ *$/, "", $1);
76                                 sub(/ /, "_", $1);
77                                 sub(/^ */, "", $2);
78                                 print $1 "=\"" $2 "\"";
79                 }')
80         fi
81
82         if [[ -z ${Volume} ]] ; then
83                 echo "Unable to parse Volume out of ISO"
84                 iso-info ${dev}
85         fi
86
87         for v in Application Preparer Publisher System Volume Volume_Set ; do
88                 echo "${v}='${!v}'"
89         done > "${out}/.${Volume}.vars.sh"
90 else
91         [[ -z ${Volume} ]] && err "Need to specify -n with --modify/--mkiso"
92         . "${out}/.${Volume}.vars.sh" || exit 1
93 fi
94
95 e() {
96         for a ; do
97                 [[ ${a} == *" "* || ${#a} == 0 ]] && fmt='"%s"' || fmt='%s'
98                 printf "${fmt} " "${a}"
99         done
100         echo
101         "$@"
102 }
103
104 #
105 # Mirror the disc w/ddrescue which tolerates errors.
106 #
107 raw_read_dvd() {
108         echo "Imaging disc"
109         local raw=".${Volume}.raw"
110         local log="${raw}.log"
111         local opts=(
112                 -v -b 2048 ${dev} "${raw}" "${log}"
113         )
114         ddrescue -p -n "${opts[@]}"
115         ddrescue -d -r 3 "${opts[@]}"
116         ddrescue -d -r 3 -R "${opts[@]}"
117         dev=${raw}
118 }
119
120 #
121 # Backup the disc.
122 #
123 backup_dvd() {
124         echo "Backing up: ${Volume}"
125         local opts=(
126                 -i ${dev}
127                 -o "${out}"
128                 -n "${Volume}"
129         )
130         sync
131         if ! e dvdbackup -M "${opts[@]}" ; then
132                 e dvdbackup -F "${opts[@]}" || exit 1
133         fi
134         sync
135 }
136
137 #
138 # Modify some of the runtime settings.
139 #
140 bytes_get() {
141         local pfx='0x' sep
142         while [[ $# -gt 3 ]]; do
143                 case $1 in
144                 "-p") pfx=$2;;
145                 "-s") sep=$2;;
146                 *) break;;
147                 esac
148                 shift 2
149         done
150         printf '%b' "${pfx}"
151
152         local file=$1 off=$2 num=${3:-1}
153         hexdump -v -n ${num} -s $((${off})) -e "1/1 \"%02x${sep}\"" "${file}"
154 }
155 bytes_set() {
156         local file=$1 off=$2
157         shift 2
158         (for b; do printf "\x${b#0x}"; done) | \
159                 dd of="${file}" \
160                         bs=1 seek=$((${off})) \
161                         conv=notrunc status=none
162 }
163 dvdsec() {
164         # DVDs have sectors of 2048 bytes (2^11).
165         local file=$1 off=$2 num=${3:-4}
166         echo $(( $(bytes_get "${file}" "${off}" "${num}") << 11 ))
167 }
168
169 _check_ifo() {
170         # Back up the .IFO files before we edit them.
171         local ifo=$1 magic=$2
172         [[ -e ${ifo} ]] || return 1
173
174         if [[ $(bytes_get "${ifo}" 0 12) != "0x${magic}" ]] ; then
175                 echo "not a VMG IFO file: ${ifo}"
176                 return 1
177         fi
178         if [[ ! -e ${ifo}.bak ]] ; then
179                 cp -a "${ifo}" "${ifo}.bak"
180         fi
181
182         return 0
183 }
184
185 check_changes() {
186         [[ -e ifodump ]] || return 0
187
188         local file=$1
189         ./ifodump -f "${file}.bak" > "${file}.bak.dmp"
190         ./ifodump -f "${file}" > "${file}.dmp"
191         vapier-diff "${file}.bak.dmp" "${file}.dmp" | sed -e 1d -e 2d > "${file}.diff"
192         local out=$(
193                 grep '^[+-]' "${file}.diff" | \
194                         grep -v \
195                                 -e '^-VMG Category:.*' \
196                                 -e '^+VMG Category: 00000000 (Region Code=ff)' \
197                                 -e '^[-+].*Title playback type:' \
198                                 -e '^-.*Title or time play:1' \
199                                 -e '^+.*Title or time play:0' \
200                                 -e '^-Prohibited user operations:' \
201                                 -e '^+Prohibited user operations: Angle Change, $' \
202                                 -e '^+Prohibited user operations: None$' \
203                         | egrep -v -e '^[-+]([0-9a-f]{2} )+$'
204         )
205         rm "${file}".{{,bak.}dmp,diff}
206         if [[ -z ${out} ]] ; then
207                 return 0
208         else
209                 echo "error in ifo modification:"
210                 echo "${out}"
211                 return 1
212         fi
213 }
214
215 process_pgci() {
216         # http://dvdnav.mplayerhq.hu/dvdinfo/pgc.html
217         local file=$1 pgci_off=$2 indent=$3
218         local num_pgcs p pgc_off off
219
220         num_pgcs=$(bytes_get "${file}" ${pgci_off} 2)
221         for (( p = 0; p < num_pgcs; ++p )) ; do
222                 pgc_off=$((
223                         pgci_off +
224                         $(bytes_get "${file}" $(( pgci_off + 8 + 8 * p + 4 )) 4)
225                 ))
226                 off=$(( pgc_off + 8 ))
227                 pgc_puo=$(bytes_get "${file}" ${off} 4)
228                 printf '%bpgc #%2i @ %#06x: %s: ' "${indent}" ${p} ${vmgm_pgc_off} ${pgc_puo}
229                 new_pgc_puo=$(( pgc_puo & (1 << 22) )) # Keep angle field.
230                 if [[ ${pgc_puo} -ne ${new_pgc_puo} ]] ; then
231                         bytes_set "${file}" ${off} 0 $(printf '%x' $(( new_pgc_puo >> 16 ))) 0 0
232                         echo "cleared"
233                 else
234                         echo "nothing to do"
235                 fi
236         done
237 }
238
239 process_pgci_ut() {
240         # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vts.html#pgciut
241         local file=$1 pgci_ut_off=$2
242         local num_langs l off
243
244         num_langs=$(bytes_get "${file}" ${pgci_ut_off} 2)
245         for (( l = 0; l < num_langs; ++l )) ; do
246                 off=$((
247                         pgci_ut_off +
248                         $(bytes_get "${file}" $(( pgci_ut_off + 8 + 8 * l + 4 )) 4)
249                 ))
250                 printf '\tclearing lang #%i @ %#06x\n' ${l} ${off}
251                 process_pgci "${file}" ${off} '\t\t'
252         done
253 }
254
255 process_vmgi() {
256         # Back up the .IFO files before we edit them.
257         local vmgi=$1
258         _check_ifo "${vmgi}" "445644564944454f2d564d47" || return 1
259
260         echo "${vmgi}"
261
262         # Clear the region code.
263         # http://dvdnav.mplayerhq.hu/dvdinfo/ifo.html
264         # 0x23 - region code restrict byte - set to 0x00 for region free.
265         echo "clearing region code"
266         bytes_set "${vmgi}" 0x23 0x00
267
268         # Clear all Prohibited User Operations (PUOs) in TT_SRPT (Uop1 & Uop0).
269         # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vmg.html
270         echo "clearing PUOs in TT_SRPT"
271         tt_srpt_sec=$(dvdsec "${vmgi}" 0xC4)
272
273         num_titles=$(bytes_get "${vmgi}" ${tt_srpt_sec} 2)
274         for (( t = 0; t < num_titles; ++t )) ; do
275                 off=$(( tt_srpt_sec + 8 + (t * 12) ))
276                 printf "\ttitle type #%2i @ %#06x: " "${t}" "${off}"
277                 title_type=$(bytes_get "${vmgi}" ${off} 1)
278                 uop0=$(( title_type & 1 ))
279                 uop1=$(( title_type & 2 ))
280                 printf "%s (uop1:%i uop0:%i): " ${title_type} ${uop1} ${uop0}
281                 new_title_type=$(( title_type & ~3 ))
282                 if [[ ${title_type} -ne ${new_title_type} ]] ; then
283                         bytes_set "${vmgi}" ${off} $(printf '%x' ${new_title_type})
284                         echo "cleared"
285                 else
286                         echo "nothing to do"
287                 fi
288         done
289
290         # Clear all the PUOs in the PGCs.  But Preserve the angle bit.
291         #   Note: Removal of the angle PUO can cause certain standalone players to
292         #   display the "angle" icon during playback. Removal of the angle PUO
293         #   whilst permitted is therefore not recommended.
294         # http://dvdnav.mplayerhq.hu/dvdinfo/uops.html
295         #fp_pgc_addr=$(bytes_get "${vmgi}" 0x84 4)
296         #num_pgcs=$(bytes_get "${vmgi}" $(( fp_pgc_sec + 1 )) 1)
297         echo "clearing PUOs in PGCs"
298         vmgm_pgci_ut_sec=$(dvdsec "${vmgi}" 0xC8)
299         process_pgci_ut "${vmgi}" ${vmgm_pgci_ut_sec}
300 }
301
302 process_vtsi() {
303         # Back up the .IFO files before we edit them.
304         local vtsi=$1
305         _check_ifo "${vtsi}" "445644564944454f2d565453" || return 1
306
307         echo "${vtsi}"
308
309         echo "clearing PUOs in PGCs"
310
311         vts_pgci_sec=$(dvdsec "${vtsi}" 0xCC)
312         process_pgci "${vtsi}" ${vts_pgci_sec} '\t'
313
314         vtsm_pgci_ut_sec=$(dvdsec "${vtsi}" 0xD0)
315         process_pgci_ut "${vtsi}" ${vtsm_pgci_ut_sec}
316 }
317
318 modify_dvd() {
319         local out_vts_dir="${out}/${Volume}/VIDEO_TS"
320         local vmgi vtsi
321
322         if ${doit_errors} ; then
323                 echo "skipping modifications due to possible errors in files"
324                 return
325         fi
326
327         for vmgi in "${out_vts_dir}"/VIDEO_TS.{IFO,BUP} ; do
328                 process_vmgi "${vmgi}"
329                 check_changes "${vmgi}" || exit 1
330         done
331
332         for vtsi in "${out_vts_dir}"/VTS_??_?.{IFO,BUP} ; do
333                 process_vtsi "${vtsi}"
334                 check_changes "${vtsi}" || exit 1
335         done
336 }
337
338 #
339 # Finally, create a new iso.
340 #
341 mkiso_dvd() {
342         set -- mkisofs -quiet -dvd-video -no-bak \
343                 -A "${Application}" \
344                 -p "${Preparer}" \
345                 -publisher "${Publisher}" \
346                 -sysid "${System}" \
347                 -V "${Volume}" \
348                 -volset "${Volume_Set}" \
349                 -o "${Volume}.iso" "${Volume}"
350         sh="${out}/.${Volume}.sh"
351         (
352         echo "#!/bin/sh"
353         echo "cd '${out}' || exit 1"
354         echo ". './.${Volume}.vars.sh'"
355         printf 'set -- '
356         printf '%q ' "$@"
357         echo
358         echo 'echo "$@"; exec "$@"'
359         ) > "${sh}"
360         chmod a+x "${sh}"
361         "${sh}" || exit
362         sync
363         md5sum "${out}/${Volume}.iso" > "${out}/${Volume}.md5"
364
365         du -h "${out}/${Volume}.iso"
366         sudo chattr +i "${out}/${Volume}.iso"
367         sync
368 }
369
370 ${doit_errors} && raw_read_dvd
371 ${doit_backup} && backup_dvd
372 ${doit_modify} && modify_dvd
373 ${doit_mkiso} && mkiso_dvd
374
375 ${doit_backup} && eject
376 exit 0