/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	#---------------------------------------------------------------------