/var/lib/sorcery/modules/libtablet
1 #!/bin/bash
2 #---------------------------------------------------------------------
3 ## @Synopsis Functions for dealing with tablet
4 ## @Copyright (C) 2004 The Source Mage Team <http://www.sourcemage.org>
5 ## <pre>
6 ## tablet layout version 0:
7 ## layout 0 is anything unversioned with all its problems, if a
8 ## tablet like this is seen it should be updated to version 1,
9 ## the defects that exist are enumerated below, future tablet
10 ## versions will not require this as they will be more fully
11 ## documented and formal accessor functions will deal with interfacing
12 ## with them
13 ##
14 ## tablet layout version 1:
15 ##
16 ## $TABLET_PATH/$SPELL/<timestamp>/
17 ## build_api
18 ## depends
19 ## grimoire_name
20 ## grimoire/<all files from the grimoire dir>
21 ## logs/[install,md5sum,compile] (links to real files)
22 ## roots (all important FOO_ROOT values)
23 ## section/<all files from the section dir>
24 ## section_name
25 ## sources (the sources and urls used)
26 ## spell/<all spell files>
27 ## spell_config
28 ## spell_config.p
29 ## status (installed or held)
30 ## tb_version (tablet version)
31 ## updated (value of $UPDATED)
32 ## patchlevel (value of $PATCHLEVEL) (optional,
33 ## defaults to 0)
34 ## security_patch (value of $SECURITY_PATCH)
35 ## (optional, defaults to 0, name still undecided upon)
36 ## updated (value of $UPDATED)
37 ## version (value of $VERSION)
38 ## cache symlink to cache archive
39 ##
40 ## known defects pre tablet version 1 (and the functions that fix them) :
41 ## no version file : tablet_0_repair_version
42 ## no updated file : tablet_0_repair_updated
43 ## spell/<spellname>/<spell files>, should be spell/<spell files>
44 ## tablet_0_repair_spell DONE
45 ## no tb_version : tablet_0_repair (bumps to version 1)
46 ##
47 ##
48 ## Terminology:
49 ## "the tablet" the directory $TABLET_PATH and everything in it
50 ## "tablet chapter" A spell's directory within the tablet eg:
51 ## $TABLET_PATH/$SPELL/...
52 ## "tablet page" A specific instance of a spell in a chapter eg:
53 ## $TABLET_PATH/$SPELL/<timestamp>/...
54 ## this is sometimes called a tablet dir, but I'm trying to phase that out
55 ##
56 ## Accessors Routines (thus far):
57 ## tablet_get_spell_file
58 ## tablet_get_section_file
59 ## tablet_get_grimoire_file
60 ## tablet_get_build_api
61 ## tablet_get_version
62 ## tablet_get_updated
63 ## tablet_get_patchlevel
64 ## tablet_get_security_patch
65 ## tablet_get_depends
66 ## tablet_get_sub_depends
67 ## tablet_get_rsub_depends
68 ## tablet_get_status
69 ## tablet_get_sources
70 ## tablet_get_spell_filter
71 ## tablet_get_section_filter
72 ## tablet_get_grimoire_filter
73 ## tablet_load_roots
74 ## tablet_unload_roots
75 ## tablet_get_tb_version
76 ## tablet_get_spell_name_from_path
77 ##
78 ## Cleanse routines:
79 ## tablet_cleanse_tablet : Fix the whole tablet
80 ## tablet_cleanse_chapter : Fix a chapter
81 ## tablet_coalesce_files : hardlink identical files to save space
82 ## tablet_fix_duplicates
83 ## determine if a tablet points back at itself through the install log
84 ## </pre>
85 #---------------------------------------------------------------------
86
87
88 ################################### Accessors ########################
89
90 function tablet_get_version() {
91 local tb_dir=$1 tb_version value
92 tablet_get_tb_version $tb_dir tb_version
93 [[ $? != 0 ]] && return 1
94 case $tb_version in
95 1) test -f $tb_dir/version && value=$(<$tb_dir/version) || return 2 ;;
96 *) return 3 ;;
97 esac
98 eval "$2=\"$value\""
99 return 0
100 }
101
102 function tablet_get_updated() {
103 local tb_dir=$1 tb_version value
104 tablet_get_tb_version $tb_dir tb_version
105 [[ $? != 0 ]] && return 1
106 case $tb_version in
107 1) test -f $tb_dir/updated && value=$(<$tb_dir/updated) || return 2 ;;
108 *) return 3 ;;
109 esac
110 eval "$2=\"$value\""
111 return 0
112 }
113
114 function tablet_get_patchlevel() {
115 local tb_dir=$1 tb_version value
116 tablet_get_tb_version $tb_dir tb_version
117 [[ $? != 0 ]] && return 1
118 case $tb_version in
119 1) test -f $tb_dir/patchlevel && value=$(<$tb_dir/patchlevel) || value=0 ;;
120 *) return 3 ;;
121 esac
122 # default value is 0 for patchlevel
123 eval "$2=\"$value\""
124 return 0
125 }
126
127 function tablet_get_security_patch() {
128 local tb_dir=$1 tb_version value
129 tablet_get_tb_version $tb_dir tb_version
130 [[ $? != 0 ]] && return 1
131 case $tb_version in
132 1) test -f $tb_dir/security_patch && value=$(<$tb_dir/security_patch) || value=0;;
133 *) return 3 ;;
134 esac
135 # default value is 0 for security_patch
136 eval "$2=\"$value\""
137 return 0
138 }
139
140 function tablet_get_build_api() {
141 local tb_dir=$1 tb_version value
142 tablet_get_tb_version $tb_dir tb_version
143 [[ $? != 0 ]] && return 1
144 case $tb_version in
145 1) test -f $tb_dir/build_api && value=$(<$tb_dir/build_api) || return 2 ;;
146 *) return 3 ;;
147 esac
148 eval "$2=\"$value\""
149 return 0
150 }
151
152 function tablet_get_depends() {
153 local tb_dir=$1 tb_version value
154 tablet_get_tb_version $tb_dir tb_version
155 [[ $? != 0 ]] && return 1
156 case $tb_version in
157 1) test -f $tb_dir/depends && value=$tb_dir/depends|| return 2 ;;
158 *) return 3 ;;
159 esac
160 eval "$2=\"$value\""
161 return 0
162 }
163
164 function tablet_get_sub_depends() {
165 local tb_dir=$1 tb_version value
166 tablet_get_tb_version $tb_dir tb_version
167 [[ $? != 0 ]] && return 1
168 case $tb_version in
169 1) test -f $tb_dir/sub_depends && value=$tb_dir/sub_depends|| return 2 ;;
170 *) return 3 ;;
171 esac
172 eval "$2=\"$value\""
173 return 0
174 }
175
176 function tablet_get_rsub_depends() {
177 local tb_dir=$1 tb_version value
178 tablet_get_tb_version $tb_dir tb_version
179 [[ $? != 0 ]] && return 1
180 case $tb_version in
181 1) test -f $tb_dir/rsub_depends && value=$tb_dir/rsub_depends|| return 2 ;;
182 *) return 3 ;;
183 esac
184 eval "$2=\"$value\""
185 return 0
186 }
187 function tablet_get_status() {
188 local tb_dir=$1 tb_version value
189 tablet_get_tb_version $tb_dir tb_version
190 [[ $? != 0 ]] && return 1
191 case $tb_version in
192 1) test -f $tb_dir/status && value=$(<$tb_dir/status) || return 2 ;;
193 *) return 3 ;;
194 esac
195 eval "$2=\"$value\""
196 return 0
197 }
198
199 function tablet_get_sources() {
200 local tb_dir=$1 tb_version value
201 tablet_get_tb_version $tb_dir tb_version
202 [[ $? != 0 ]] && return 1
203 case $tb_version in
204 1) test -f $tb_dir/sources && value=$(<$tb_dir/sources) || return 2 ;;
205 *) return 3 ;;
206 esac
207 eval "$2=\"$value\""
208 return 0
209 }
210
211 function tablet_get_spell_file() {
212 local tb_dir=$1 tb_version value
213 tablet_get_tb_version $tb_dir tb_version
214 [[ $? != 0 ]] && return 1
215 case $tb_version in
216 1) test -f $tb_dir/spell/$2 && value=$tb_dir/spell/$2 || return 2 ;;
217 *) return 3 ;;
218 esac
219 eval "$3=\"$value\""
220 return 0
221 }
222
223 function tablet_get_persistent_config() {
224 local tb_dir=$1 tb_version value
225 tablet_get_tb_version $tb_dir tb_version
226 [[ $? != 0 ]] && return 1
227 case $tb_version in
228 1) test -f $tb_dir/spell_config.p && value=$tb_dir/spell_config.p || return 2 ;;
229 *) return 3 ;;
230 esac
231 eval "$2=\"$value\""
232 return 0
233 }
234
235 function tablet_get_section_file () {
236 local tb_dir=$1 tb_version value
237 tablet_get_tb_version $tb_dir tb_version
238 [[ $? != 0 ]] && return 1
239 case $tb_version in
240 1) test -f $tb_dir/section/$2 && value=$tb_dir/section/$2 || return 2 ;;
241 *) return 3 ;;
242 esac
243 eval "$3=\"$value\""
244 return 0
245 }
246
247 function tablet_get_grimoire_file() {
248 local tb_dir=$1 tb_version value
249 tablet_get_tb_version $tb_dir tb_version
250 [[ $? != 0 ]] && return 1
251 case $tb_version in
252 1) test -f $tb_dir/grimoire/$2 && value=$tb_dir/grimoire/$2 || return 2 ;;
253 *) return 3 ;;
254 esac
255 eval "$3=\"$value\""
256 return 0
257 }
258
259 #---
260 ## return section name from tablet
261 #---
262 function tablet_get_section_name() {
263 local tb_dir=$1 tb_version value
264 tablet_get_tb_version $tb_dir tb_version
265 [[ $? != 0 ]] && return 1
266 case $tb_version in
267 1) test -f $tb_dir/section_name && value=$(<$tb_dir/section_name) || return 2 ;;
268 *) return 3 ;;
269 esac
270 eval "$2=\"$value\""
271 return 0
272 }
273
274 function tablet_get_grimoire_name() {
275 local tb_dir=$1 tb_version value
276 tablet_get_tb_version $tb_dir tb_version
277 [[ $? != 0 ]] && return 1
278 case $tb_version in
279 1) test -f $tb_dir/grimoire_name && value=$(<$tb_dir/grimoire_name) || return 2 ;;
280 *) return 3 ;;
281 esac
282 eval "$2=\"$value\""
283 return 0
284 }
285
286 function tablet_get_log_file() {
287 local tb_dir=$1 tb_version value
288 tablet_get_tb_version $tb_dir tb_version
289 [[ $? != 0 ]] && return 1
290 case $tb_version in
291 1) test -f $tb_dir/logs/$2 && value=$tb_dir/logs/$2 || return 2 ;;
292 *) return 3 ;;
293 esac
294 eval "$3=\"$value\""
295 return 0
296 }
297
298 function tablet_get_spell_filter() {
299 tablet_get_spell_file $1 $2 $3
300 }
301 function tablet_get_section_filter() {
302 tablet_get_section_file $1 $2 $3
303 }
304 function tablet_get_grimoire_filter() {
305 tablet_get_grimoire_file $1 $2 $3
306 }
307
308 #---
309 ## get path to roots
310 #---
311 function tablet_get_roots() {
312 local tb_dir=$1 tb_version value
313 tablet_get_tb_version $tb_dir tb_version
314 [[ $? != 0 ]] && return 1
315 case $tb_version in
316 1) test -f $tb_dir/roots && value=$tb_dir/roots|| return 2 ;;
317 *) return 3 ;;
318 esac
319 eval "$2=\"$value\""
320 return 0
321 }
322
323 function tablet_load_roots() {
324 local tb_dir=$1 tb_version
325 tablet_get_tb_version $tb_dir tb_version
326 [[ $? != 0 ]] && return 1
327 case $tb_version in
328 1) test -f $tb_dir/roots && source $tb_dir/roots || return 2 ;;
329 *) return 3 ;;
330 esac
331 source $STATE_CONFIG
332 }
333
334 function tablet_unload_roots() {
335 source $ROOTS_CONFIG
336 source $STATE_CONFIG
337 }
338
339 function tablet_get_tb_version() {
340 local tb_dir=$1
341 if ! [[ $tb_dir ]] ; then
342 error_message "nothing passed in!"
343 return 1
344 fi
345 if ! test -f $tb_dir/tb_version ||
346 [[ "$(< $tb_dir/tb_version)" == 0 ]] ; then
347 tablet_0_repair $tb_dir || return 1
348 fi
349 local ____tb_version=$(<$tb_dir/tb_version)
350 if test $____tb_version -gt $TABLET_MAX_VERSION; then
351 error_message "${PROBLEM_COLOR}This sorcery is too old for tablet version" \
352 "$tb_version! Please update to the newer version of sorcery" \
353 "or recast this spell${DEFAULT_COLOR}"
354 return 255
355 fi
356 eval "$2=\"$____tb_version\""
357 return 0
358 }
359
360 #---------------------------------------------------------------------
361 ## determine the spell associated with a tablet path
362 ## @param tablet path
363 ## @stdout spell name
364 ##
365 ## tablet paths are $TABLET_PATH/<spell>/<timestamp>
366 ## dirname of that is $TABLET_PATH/<spell>
367 ## basename of that is <spell>
368 #---------------------------------------------------------------------
369 function tablet_get_spell_name_from_path() {
370 [[ $1 ]] || return
371 local tmp
372 smgl_dirname "$1" tmp
373 smgl_basename "$tmp" $2 # quotes deliberatly left off, (hack for speed)
374 }
375
376 ######################################################################
377
378 #---------------------------------------------------------------------
379 ## setup a unique tablet directory
380 ## @param spell name
381 ## @stdout path to unique tablet directory
382 ## @return 0 if the directory was made, 1 if not
383 #---------------------------------------------------------------------
384 function tablet_get_path() {
385 local SPELL=$1
386 local tb_dir=$TABLET_PATH/$SPELL/$(date +%Y%m%d%H%M%S)
387 local sleep_time
388 local made
389
390 mkdir -p $TABLET_PATH/$SPELL &&
391 for (( i=0 ; $i < 20 ; i++ )) ; do
392 if mkdir $tb_dir > /dev/null; then
393 made=1
394 break
395 fi
396 let sleep_time=$RANDOM%3
397 sleep $sleep_time
398 tb_dir=$TABLET_PATH/$SPELL/$(date +%Y%m%d%H%M%S)
399 done
400 if [[ $made ]]; then
401 echo $tb_dir
402 return 0
403 fi
404 return 1
405 }
406
407 #----------------------------------------------------------------------
408 ## @param spell name
409 ## @param upvar
410 ## @param timestamp (optional)
411 ##
412 ## @global TABLET_IGNORE if set means that there isnt a tablet for this
413 ## @global and somewhere up the stack knows this<br />
414 ## @global TABLET_SPELL_DIR if set, is the value of the tablet for this
415 ## @global spell, because somewhere up the stack it was just created (so dont bother
416 ## @global looking for it)
417 ##
418 ## the idea for the two globals is to simplify the layers in-between
419 ## and skip searching when it isn't necessary
420 #----------------------------------------------------------------------
421 function tablet_find_spell_dir() {
422 [[ $TABLET_IGNORE ]] && return 1
423 if [[ $TABLET_SPELL_DIR ]] ; then
424 eval "$2=\"$TABLET_SPELL_DIR\""
425 return 0
426 fi
427 local SPELL=$1
428 if ! test -d $TABLET_PATH; then
429 mkdir -p $TABLET_PATH
430 return 1
431 fi
432 local __spell_dir
433 local base_dir=$TABLET_PATH/$SPELL
434 if ! [[ $base_dir ]] || ! test -d $base_dir ; then
435 return 1
436 fi
437
438 if [[ $3 ]] ; then
439 local timestamp=$3
440 if test -d $base_dir/$timestamp; then
441 __spell_dir=$base_dir/$timestamp
442 eval "$2=\"$__spell_dir\""
443 return 0
444 fi
445 return 1 # requested a dir that was not there
446 fi
447 # print the newest one
448 local BREAK
449 function tablet_find_spell_dir_sub() {
450 tablet_is_spell_version_installed $SPELL $1 quiet &&
451 tablet_does_tablet_point_to_itself $1 quiet &&
452 __spell_dir=$1 &&
453 BREAK=yes
454 }
455 iterate tablet_find_spell_dir_sub $'\n' \
456 "$(find $base_dir -mindepth 1 -maxdepth 1 -type d)"
457 if ! [[ $__spell_dir ]] ; then
458 # base dir exists but is empty ( ideally this wont happen, but play it safe)
459 return 1
460 fi
461 eval "$2=\"$__spell_dir\""
462 return 0
463 }
464
465 #----------------------------------------------------------------------
466 ## @param spell name
467 ## @param upvar
468 ## quicker dirtier version of above without special checks since the
469 ## tablet in a cache tarball has nothing to do with the installed system
470 #----------------------------------------------------------------------
471 function tablet_find_resurrect_dir() {
472 local SPELL=$1
473 if ! test -d $TABLET_PATH; then
474 return 1
475 fi
476 local __spell_dir
477 local base_dir=$TABLET_PATH/$SPELL
478 if ! [[ $base_dir ]] || ! test -d $base_dir ; then
479 return 1
480 fi
481
482 local __spell_dir=$(find $base_dir -mindepth 1 -maxdepth 1 -type d|head -n 1)
483 if ! [[ $__spell_dir ]] ; then
484 # base dir exists but is empty ( ideally this wont happen, but play it safe)
485 return 1
486 fi
487 eval "$2=\"$__spell_dir\""
488 return 0
489 }
490
491 #----------------------------------------------------------------------
492 ## @param tablet dir
493 ## @globals everything that comes with a spell...
494 #----------------------------------------------------------------------
495 function tablet_install_spell_files() {
496 pushd $1 &>/dev/null || {
497 message "Failed to enter $1"
498 return 1
499 }
500
501 # this is tablet version 1
502 echo 1 > tb_version
503
504 # directories
505 mkdir spell section grimoire
506
507 cp -R $SCRIPT_DIRECTORY/* spell
508
509 local section_files=$(find $SECTION_DIRECTORY -maxdepth 1 -type f)
510 if [[ $section_files ]] ; then cp $section_files section; fi
511
512 local grimoire_files=$(find -L $GRIMOIRE -maxdepth 1 -type f)
513 if [[ $grimoire_files ]] ; then cp $grimoire_files grimoire; fi
514
515 # magic values we want to be able to easily look up
516 echo $VERSION > version
517 echo $UPDATED > updated
518 echo ${PATCHLEVEL:-0} > patchlevel
519 echo ${SECURITY_PATCH:-0} > security_patch
520 echo $BUILD_API > build_api
521 echo $SECTION > section_name
522 echo $GRIMOIRE_NAME > grimoire_name
523 echo installed > status # this could be held or something else
524
525 # SPELL_CONFIG and persistent variables
526 test -e $SPELL_CONFIG && cp $SPELL_CONFIG spell_config
527 test -e $SPELL_CONFIG.p && cp ${SPELL_CONFIG}.p spell_config.p
528
529 # depends info
530 get_uncommitted_depends_file $SPELL spell_depends &&
531 test -e $spell_depends && cp $spell_depends depends
532
533 # sub-depends we provide
534 local sub_depends_file rsub_depends_file
535 get_uncommitted_sub_depends_file $SPELL sub_depends_file &&
536 test -e $sub_depends_file && cp $sub_depends_file sub_depends
537 # get sub-depend we request
538 get_uncommitted_rsub_depends_file $SPELL sub_depends_file &&
539 test -e $rsub_depends_file && cp $sub_depends_file rsub_depends
540
541 # logs
542 mkdir logs
543 ln -s $INST_LOG logs/install
544 ln -s $MD5_LOG logs/md5sum
545 ln -s $C_LOG_COMP logs/compile
546
547 # cache archive
548 if [ "$ARCHIVE" == "on" ]; then
549 ln -s $CACHE_COMP cache
550 fi
551
552 # roots
553 echo INSTALL_ROOT="$INSTALL_ROOT" > roots
554 echo TRACK_ROOT="$TRACK_ROOT" >> roots
555 echo STATE_ROOT="$STATE_ROOT" >> roots
556
557 # all the sources and their urls
558 get_spell_files_and_urls > sources
559
560 popd &>/dev/null
561 }
562
563
564 #---------------------------------------------------------------------
565 ## this is to set a spell based on whats in the installed grimoire
566 ## possibly for re-casting, not sure what else...
567 ##
568 ## Note that this function does not use the tablet_get accessors for
569 ## efficiency.
570 #---------------------------------------------------------------------
571 function tablet_set_spell() {
572 codex_clear_current_spell
573 SPELL=$1
574 if [[ $2 ]] && test -d $2; then
575 SPELL_DIRECTORY=$2
576 else
577 tablet_find_spell_dir $SPELL SPELL_DIRECTORY || return 1
578 TABLET_PAGE=$SPELL_DIRECTORY
579 fi
580
581 VERSION=$(<$SPELL_DIRECTORY/version)
582
583 # Directories
584 SCRIPT_DIRECTORY=$SPELL_DIRECTORY/spell
585 SECTION_DIRECTORY=$SPELL_DIRECTORY/section
586 GRIMOIRE=$SPELL_DIRECTORY/grimoire
587 if [[ -f $SPELL_DIRECTORY/grimoire_name ]]; then
588 GRIMOIRE_NAME=$(<$SPELL_DIRECTORY/grimoire_name)
589 fi
590
591 # Names
592 SECTION=$(<$SPELL_DIRECTORY/section_name)
593
594 SPELL_CONFIG="$SPELL_DIRECTORY/spell_config"
595 if [ -f $SPELL_CONFIG ]; then
596 . $SPELL_CONFIG > /dev/null 2> /dev/null
597 fi
598
599 persistent_load
600 . $SPELL_DIRECTORY/DETAILS 1>/dev/null 2>&1
601 persistent_clear
602
603 BUILD_API=$(<$SPELL_DIRECTORY/build_api)
604 # if BUILD_API isnt set something is wrong, but just be safe
605 [[ -z $BUILD_API ]] && BUILD_API=2
606
607 INST_LOG=$SPELL_DIRECTORY/logs/install
608 MD5_LOG=$SPELL_DIRECTORY/logs/md5sum
609 C_LOG_COMP=$SPELL_DIRECTORY/logs/compile
610
611
612 local given_tablet_path=$TABLET_PATH
613
614 . $SPELL_DIRECTORY/roots
615 . $STATE_CONFIG
616
617 TABLET_PATH=$given_tablet_path
618
619 return 0
620 }
621
622
623 ############################ REPAIR files #############################
624 #---------------------------------------------------------------------
625 ## Import repair files for all tablet pages
626 #---------------------------------------------------------------------
627 function tablet_import_repair_files() { (
628 local tablet_path=$1
629 local dir rc chapter page
630
631 codex_create_in_memory_cache_all -i spell_lookup_hash
632
633 shopt -s nullglob # so REPAIR^* evaluates to nothing if no REPAIR files
634
635 for chapter in $tablet_path/* ; do
636 test -d $chapter || continue
637 for page in $chapter/*; do
638 test -d $page || continue
639 tablet_import_repair_files_page "$page" spell_lookup_hash
640 done
641 done
642 ) }
643
644 #---------------------------------------------------------------------
645 ## Import repair files for a specific tablet page, expects caller
646 ## to have spell_lookup_hash setup for a spell lookup, and nullglob set.
647 #---------------------------------------------------------------------
648 function tablet_import_repair_files_page() {
649 local page=$1
650
651 local spell spell_dir
652 tablet_get_spell_name_from_path "$page" spell
653 hash_get_ref $2 $spell spell_dir
654 codex_get_spell_paths "$spell_dir"
655
656 local name key
657 local loaded
658 local spell_version spell_updated codex_md5 repair_file tablet_file tablet_md5
659 for repair_file in $SPELL_DIRECTORY/REPAIR^*; do
660 [[ -f "$repair_file" ]] || continue
661 name=$(smgl_basename "$repair_file"|cut -f3- -d^)
662 key=$(smgl_basename "$repair_file"|cut -f2 -d^)
663 [[ $name ]] || continue
664 [[ $key ]] || continue
665 tablet_check_repair_file spell
666 done
667
668 for repair_file in $SECTION_DIRECTORY/REPAIR^*; do
669 [[ -f "$repair_file" ]] || continue
670 name=$(smgl_basename "$repair_file"|cut -f3- -d^)
671 key=$(smgl_basename "$repair_file"|cut -f2 -d^)
672 [[ $name ]] || continue
673 [[ $key ]] || continue
674 tablet_check_repair_file section
675 done
676
677 for repair_file in $GRIMOIRE/REPAIR^*; do
678 [[ -f "$repair_file" ]] || continue
679 name=$(smgl_basename "$repair_file"|cut -f3- -d^)
680 key=$(smgl_basename "$repair_file"|cut -f2 -d^)
681 [[ $name ]] || continue
682 [[ $key ]] || continue
683 tablet_check_repair_file grimoire
684 done
685 }
686
687 #---------------------------------------------------------------------
688 ## Private subroutine for tablet_import_repair_files_page, do not call
689 ## from anywhere else.
690 #---------------------------------------------------------------------
691 function tablet_check_repair_file() {
692 local type=$1
693 local replace=0
694 local tablet_file
695
696 if ! [[ "$loaded" ]] ; then
697 tablet_get_version "$page" spell_version
698 tablet_get_updated "$page" spell_updated
699 tablet_get_patchlevel "$page" spell_patchlevel
700 loaded=done
701 fi
702
703 case $type in
704 spell) tablet_get_spell_file $page $name tablet_file;;
705 section) tablet_get_section_file $page $name tablet_file;;
706 grimoire) tablet_get_grimoire_file $page $name tablet_file;;
707 *) return 1 ;;
708 esac
709
710 if ! [[ $tablet_file ]] ; then
711 if [[ $key == none ]] || [[ $key == all ]]; then
712 # WARNING: tb_version specific path derivation here:
713 tablet_file=$page/$type/$name
714 replace=1
715 fi
716 else
717 codex_md5=$(md5sum $repair_file|cut -f1 -d' ')
718 tablet_md5=$(md5sum $tablet_file|cut -f1 -d' ')
719 if [[ $key == "all" ]] ||
720 [[ $spell_version == $key ]] ||
721 [[ $spell_updated == $key ]] ||
722 [[ $spell_patchlevel == $key ]] ||
723 [[ $tablet_md5 == $key ]] ; then
724 replace=2
725 fi
726 fi
727
728 if [[ $replace == 1 ]] ||
729 { [[ $replace == 2 ]] && [[ $codex_md5 != $tablet_md5 ]] ; } ; then
730 message "${MESSAGE_COLOR}Tablet Repair: replacing${DEFAULT_COLOR}"
731 message "$tablet_file\nwith repair file \n$repair_file"
732 cp $repair_file $tablet_file
733 else
734 debug libtablet "Tablet Repair: not replacing $tablet_file with $repair_file ($repair, $codex_md5, $tablet_md5)"
735 fi
736 }
737
738 #---------------------------------------------------------------------
739 ## Creates a version cache like the one of scribe reindex-version, but
740 ## from the data found in the tablet
741 ##
742 ## @param file that will hold the cache
743 #---------------------------------------------------------------------
744 function tablet_create_version_cache() {
745 [[ -f $1 ]] && return 0
746 local file=$1
747 local page_dir
748 local spell version patchlevel security_patch updated
749 local rc=0
750 local ok_spells=( $(get_all_spells_with_status ok) )
751
752 for spell in ${ok_spells[@]}; do
753 # make sure we don't get any rare errors into our carefully crafted list
754 if tablet_find_spell_dir $spell page_dir > /dev/null; then
755 tablet_get_version $page_dir version > /dev/null
756 tablet_get_patchlevel $page_dir patchlevel > /dev/null
757 tablet_get_security_patch $page_dir security_patch > /dev/null
758 tablet_get_updated $page_dir updated > /dev/null
759 echo "$spell ${version:-0} ${patchlevel:-0} ${security_patch:-0} ${updated:-0}"
760 elif [[ $spell == alter ]]; then
761 version=$(installed_version alter)
762 echo "$spell ${version:-0} ${patchlevel:-0} ${security_patch:-0} ${updated:-0}"
763 else
764 error_message "${PROBLEM_COLOR}Creation of the cache failed at $spell," \
765 "please run cleanse --tablet and retry. If some spells" \
766 "have unfixable tablet pages, recast them.\n" \
767 "If you're ok with slower queueing of everything just run:" \
768 "export OLD_QUEUING_METHOD=1 and you won't be bothered again.$DEFAULT_COLOR"
769 break
770 fi
771 done | sort > $file
772
773 # we can't set rc inside the loop, since it is in a subshell and
774 # the rc of break 13 still does not match what is documented in bash 4.2.24
775 if [[ ${#ok_spells[@]} != $(wc -l < $file) ]]; then
776 rm $file
777 return 1
778 fi
779 }
780
781 #---------------------------------------------------------------------
782 ## Checks for existance and on failure creates the tablet version cache
783 ##
784 ## @param cache file (usually $VERSION_STATUS)
785 #---------------------------------------------------------------------
786 function tablet_check_version_cache() {
787 [[ -z $1 ]] && return 1
788 [[ -n $OLD_QUEUING_METHOD ]] && return 0
789 local file=$1
790
791 if [[ -f $file ]] &&
792 [[ $file == $VERSION_STATUS ]] &&
793 [[ $(wc -l < $file) != $(get_all_spells_with_status ok | wc -l) ]]; then
794 error_message "${PROBLEM_COLOR}The tablet version cache is damaged, removing it!"
795 error_message "$DEFAULT_COLOR"
796 rm "$file"
797 fi
798
799 if [[ ! -f $file ]]; then
800 message "${MESSAGE_COLOR}Creating the tablet version cache, this" \
801 "can take a while ...$DEFAULT_COLOR"
802 tablet_create_version_cache "$file"
803 fi
804 }
805
806 ############################ cleanse functions ########################
807
808 #---------------------------------------------------------------------
809 ##
810 #---------------------------------------------------------------------
811 function tablet_cleanse_tablet() {
812 local tablet_path=$1
813 local backup_dir=$2
814 local file_backup_dir=$backup_dir/tablet_files
815 local dir rc
816
817 mkdir -p "$file_backup_dir"
818 for dir in $tablet_path/* ; do
819 if test -d "$dir" ; then
820 tablet_cleanse_chapter "$dir" "$backup_dir" no
821 else
822 message "$dir is not a directory! backing it up in $backup_dir"
823 mv "$dir" "$file_backup_dir"
824 fi
825 done
826 tablet_import_repair_files "$tablet_path"
827 }
828
829 #---------------------------------------------------------------------
830 ##
831 #---------------------------------------------------------------------
832 function tablet_cleanse_chapter() {
833 local sdir=$1
834 local backup_dir=$2
835 local do_repair=$3
836 local spell
837 smgl_basename "$sdir" spell
838 local page rc
839
840 mkdir -p "$backup_dir/duplicates"
841 mkdir -p "$backup_dir/chapter_files"
842 tablet_fix_duplicates "$spell" "$sdir" "$backup_dir/duplicates"
843 test -d "$sdir" || return 0
844
845
846 for page in $sdir/*; do
847 if ! test -d "$page"; then
848 message "$dir is not a directory! backing it up in $backup_dir"
849 mv "$page" "$backup_dir/chapter_files/$page.$spell"
850 elif [[ $do_repair == yes ]] ; then
851 ( # pretend we're tablet_import_repair_files
852 shopt -s nullglob
853 hash_put spell_lookup_hash "$spell" "$(codex_find_spell_by_name $spell)"
854 tablet_import_repair_files_page "$page" spell_lookup_hash
855 )
856 fi
857 done
858 }
859
860
861 #---------------------------------------------------------------------
862 ## @param spell
863 ## @param path to a tablet chapter
864 ## @param backup dir
865 #---------------------------------------------------------------------
866 function tablet_fix_duplicates() {
867 local spell=$1
868 local tbc_dir=$2
869 local backup_dir=$3
870 local tablets rc dir i
871
872 let i=0
873 for dir in $tbc_dir/*; do
874 if test -d $dir; then
875 tablets[$i]=$dir
876 let i+=1
877 fi
878 done
879
880 if test ${#tablets[*]} -eq 0 ||
881 test -z "${tablets[0]}"; then
882 message "Empty tablet chapter: $tbc_dir, removing it"
883 rmdir $tbc_dir
884 return
885 fi
886
887 for ((i=0;$i<${#tablets[*]}; i++ )) ; do
888 message "Inspecting ${tablets[$i]}"
889 tablet_is_spell_version_installed $spell ${tablets[$i]} &&
890 tablet_does_tablet_point_to_itself ${tablets[$i]} ||
891 tablet_backup_page ${tablets[$i]} $backup_dir
892 done
893
894 if ! [[ $(ls $tbc_dir) ]] ; then
895 message "Empty tablet chapter: $tbc_dir, removing it"
896 rmdir $tbc_dir
897 fi
898 }
899
900 function tablet_backup_page() {
901 local tb_dir=$1
902 local backup_dir=$2
903 local spell_name=$(tablet_get_spell_name_from_path $tb_dir)
904 mkdir -p $backup_dir/$spell_name
905 mv $tb_dir $backup_dir/$spell_name
906 }
907
908 #---------------------------------------------------------------------
909 ## check if the tablet represents a spell version thats installed
910 #---------------------------------------------------------------------
911 function tablet_is_spell_version_installed() {
912 local spell=$1
913 local tb_dir=$2
914 local quiet=$3
915 local tb_version
916 tablet_get_tb_version $tb_dir tb_version
917 [[ $? != 0 ]] && return 253
918 local version_in_tablet
919 tablet_get_version $tb_dir version_in_tablet
920 [[ $? != 0 ]] && {
921 [[ $quiet ]] || message "Unable to get version for $tb_dir"
922 return 254
923 }
924 local version_installed=$(installed_version $spell)
925 if [[ "$version_in_tablet" != "$version_installed" ]] ; then
926 if ! [[ $quiet ]] ; then
927 message "Tablet at $tb_dir represents\na version that is not installed" \
928 "on the system!"
929 message "tablet version: \"$version_in_tablet\""
930 message "installed version: \"$version_installed\""
931 fi
932 return 1
933 fi
934 return 0
935 }
936
937 #---------------------------------------------------------------------
938 ## check if a tablets install log includes itself
939 #---------------------------------------------------------------------
940 function tablet_does_tablet_point_to_itself() {
941 local rc
942 local tb_dir=$1
943 local quiet=$2
944 local tb_version
945 tablet_get_tb_version $tb_dir tb_version
946 [[ $? != 0 ]] && return 253
947
948 local tb_inst_log
949 tablet_get_log_file $tb_dir install tb_inst_log
950 [[ $? != 0 ]] && {
951 message "Unable to get install log for $tb_dir!"
952 return 254
953 }
954 tablet_load_roots $tb_dir
955 log_adjuster $tb_inst_log /dev/stdout log root 2>/dev/null| grep -q "^$tb_dir"
956 rc=$?
957 tablet_unload_roots $tb_dir
958 if [[ $rc != 0 ]] ; then
959 if ! [[ $quiet ]] ; then
960 message "Tablet at $tb_dir points to an install log that does not point" \
961 "back at $tb_dir!"
962 fi
963 fi
964 return $rc
965 }
966
967 #----------------------------------------------------------------------
968 ## @param none
969 ## @stdout none
970 ## @globals $TABLET_PATH
971 ## Intended to be used by cleanse, this routine finds identical files in
972 ## $TABLET_PATH and hardlinks identical files together in order to save
973 ## space. This is done on the assumption that 1) tablet files are treated
974 ## as read only, and 2) anyone using the tablet for write operations (they
975 ## shouldnt be) will break the hardlinks
976 #----------------------------------------------------------------------
977 function tablet_coalesce_files() {
978 local dir=${1:-$TABLET_PATH}
979 local car cdr i each
980 let i=0
981
982 local link_log=${2:-$TMP_DIR}/link_log
983
984 local total_files=$(find $dir -type f 2>/dev/null|wc -l)
985 message
986 message "Computing the md5sums of $total_files tablet files and coalescing all the duplicates..."
987 # the + makes find stack found files, so md5sum is invoked a lot less often
988 find $dir -type f -exec md5sum {} + 2>/dev/null | sort |
989 while read sum file; do
990 if [[ -z $previous_sum || $previous_sum != $sum ]]; then
991 previous_sum=$sum
992 previous_file=$file
993 else
994 if ! [[ $previous_file -ef $file ]]; then
995 echo "Linking $previous_file to $file" >> $link_log
996 ln -f $previous_file $file
997 fi
998 fi
999 let i+=1
1000 progress_bar $i $total_files
1001 done
1002 clear_line
1003 }
1004
1005 ###################### Version 0 repair functions ####################
1006
1007 #---------------------------------------------------------------------
1008 ## @param tablet dir
1009 ## Fix the various known defects with an un-versioned tablet and stuff
1010 ## a version of 1 in it.
1011 #---------------------------------------------------------------------
1012 function tablet_0_repair() {
1013 [[ $1 ]] || return 1
1014 # once all these are done, we should have a version 1 tablet
1015 tablet_0_repair_spell $1 &&
1016 tablet_0_repair_version $1 &&
1017 tablet_0_repair_updated $1 &&
1018 echo 1 > $1/tb_version || {
1019 message "Error repairing tablet at $1"
1020 return 1
1021 }
1022 return 0
1023 }
1024
1025 #---------------------------------------------------------------------
1026 ## @param tablet dir
1027 ##
1028 ## tablet may have either
1029 ## $tb_dir/spell/DETAILS or
1030 ## $tb_dir/spell/<spellname>/DETAILS
1031 ## the first is correct, the second is not, correct the problem if it exists
1032 #---------------------------------------------------------------------
1033 function tablet_0_repair_spell() {
1034 local tb_dir=$1
1035 [[ $tb_dir ]] || return 1
1036 local spell_name=$(tablet_get_spell_name_from_path $tb_dir)
1037 if test -d $tb_dir/spell/; then
1038 if test -f $tb_dir/spell/DETAILS; then
1039 return 0
1040 elif test -d $tb_dir/spell/$spell_name; then
1041 message "Repairing defected tablet spell dir in $tb_dir"
1042 mv $tb_dir/spell/$spell_name/* $tb_dir/spell/ &&
1043 rmdir $tb_dir/spell/$spell_name &&
1044 return 0
1045 else
1046 message "Corrupt spell dir in tablet, this shouldnt happen."
1047 message "Please run cleanse --tablet"
1048 return 1
1049 fi
1050 else
1051 message "Missing spell dir in tablet, this shouldnt happen."
1052 message "Please run cleanse --tablet"
1053 return 1
1054 fi
1055 }
1056
1057 #---------------------------------------------------------------------
1058 ## @param tablet dir
1059 ##
1060 ## Repair the "updated" file as it wasnt created for all pre-versioned
1061 ## tablets. Source the spell, echo $VERSION into a file named version
1062 #---------------------------------------------------------------------
1063 function tablet_0_repair_version() {
1064 local tb_dir=$1
1065 [[ $tb_dir ]] || return 1
1066 test -f $tb_dir/version ||
1067 (
1068 message "Repairing missing spell version file in $tb_dir"
1069 # note this assumes tablet_0_repair_spell has run
1070 source $tb_dir/spell/DETAILS &>/dev/null
1071 echo $VERSION > $tb_dir/version
1072 )
1073 }
1074
1075 #---------------------------------------------------------------------
1076 ## @param tablet dir
1077 ## Repair the "updated" file as it wasnt created for all pre-versioned
1078 ## tablets. Source the spell, echo $UPDATED into a file named updated
1079 #---------------------------------------------------------------------
1080 function tablet_0_repair_updated() {
1081 local tb_dir=$1
1082 [[ $tb_dir ]] || return 1
1083 test -f $tb_dir/updated ||
1084 (
1085 message "Repairing missing spell updated file in $tb_dir"
1086 # note this assumes tablet_0_repair_spell has run
1087 source $tb_dir/spell/DETAILS &>/dev/null
1088 echo $UPDATED > $tb_dir/updated
1089 )
1090 }
1091
1092 #---------------------------------------------------------------------
1093 ##=back
1094 ##
1095 ##=head1 LICENSE
1096 ##
1097 ## This software is free software; you can redistribute it and/or modify
1098 ## it under the terms of the GNU General Public License as published by
1099 ## the Free Software Foundation; either version 2 of the License, or
1100 ## (at your option) any later version.
1101 ##
1102 ## This software is distributed in the hope that it will be useful,
1103 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
1104 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1105 ## GNU General Public License for more details.
1106 ##
1107 ## You should have received a copy of the GNU General Public License
1108 ## along with this software; if not, write to the Free Software
1109 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1110 ##
1111 #---------------------------------------------------------------------