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 | #!/usr/bin/python # # Copyright 2017 Ingo Hornberger <ingo_@gmx.net> # # This software is licensed under the MIT License # # Permission is hereby granted, free of charge, to any person obtaining a copy of this # software and associated documentation files (the "Software"), to deal in the Software # without restriction, including without limitation the rights to use, copy, modify, # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to the following # conditions: # # The above copyright notice and this permission notice shall be included in all copies # or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE # OR OTHER DEALINGS IN THE SOFTWARE. # ################################################################################ # We use templates, which are checking their execution context, # to be sure, that target commands are really only executed # in the change root of the image. # # We use snar files to create incremental archives. Those files are targeted # to incremental backups, where the filestructure stays roughly the same. So # beside the name and timestamps of the files, it also relies on inodes. # # As we are packing and extracting the images for every step, the inodes are # constantly changing. As tar doesn't support to ignore inodes on incremental # backups, we are refreshing the backups in every step. # # input snar output snar output tar # level 0: <none> l0.snar l0.tar # level 1: l0.snar l1.snar l1.tar # level 2: l1.snar l2.snar l2.tar # # When we prepare the rootfs for level 2, we have to do the following: # # - tar -xf l0.tar -g l0.snar # # - tar -xf l1.tar -g l1.snar # - cp l0.snar l1.snar # - tar -cf /dev/zero -g l1.snar . # # - tar -xf l2.tar -g l2.snar # - cp l1.snar l2.snar # - tar -cf /dev/zero -g l2.snar . # ################################################################################# templateTrgCmd = "#!/bin/bash\n[ -f ../Wharfile ] && exit 2;\n%s\nexit \$$?\n" templateHostCmd = "#!/bin/bash\n[ ! -f ../Wharfile ] && exit 2;\n%s\nexit \$$?\n" makeTargets = list(); environment = list(); finalTarget = list(); # # Write a Makefile # def write_makefile(filename, dry_run, installpath='.', incremental=False): backup_levels=list() f = open(filename, 'w'); # write header f.write("ifneq (${VERBOSE},y)\n") f.write("Q=@\n") f.write("endif\n") f.write("\n") f.write("OUTPUT_FILE=%s\n" % (finalTarget[0])) f.write("\n") f.write("all: %s\n\n" % (finalTarget[0])); # write all environment variables for env in environment: l = env.lstrip().split(" ") f.write("export %s=%s\n" % (l[0], " ".join(l[1:]))); # write all targets for target in makeTargets: if 'comment' in target: f.write("# %s\n" % target['comment']); f.write("%s: %s\n" % (target['name'], " ".join(target['dep']))); # target and host commands have to extract and repack always. # simple commands don't do that. if 'trgcmd' in target or 'hostcmd' in target or 'temporary' in target: f.write("\t${Q}-mkdir $$(basename $@ .tar)\n"); if 'trgcmd' in target: cmd = templateTrgCmd % (target['trgcmd'].replace('$', '\\$$').replace('"', '\\"')); f.write("\t${Q}echo '******************************'\n"); f.write("\t${Q}echo '%s'\n" % target['comment']); f.write("\t${Q}echo '******************************'\n"); f.write("\t${Q}(echo -e \"%s\") | ${SUDO} tee .trg.sh\n" % cmd.replace('\\n', '\\\\\\n').replace('\n', '\\n').replace('!', '"\'!\'"')); f.write("\t${Q}${SUDO} chmod a+x .trg.sh\n"); if 'hostcmd' in target: cmd = templateHostCmd % (target['hostcmd'].replace('$', '\\$$').replace('"', '\\"')); f.write("\t${Q}echo '******************************'\n"); f.write("\t${Q}echo '%s'\n" % target['comment']); f.write("\t${Q}echo '******************************'\n"); f.write("\t${Q}(echo -e \"%s\") | ${SUDO} tee .hst.sh\n" % cmd.replace('\\n', '\\\\\\\\n').replace('\n', '\\n').replace('!', '"\'!\'"')); f.write("\t${Q}${SUDO} chmod a+x .hst.sh\n"); # start command here ... f.write("\t${Q}${SUDO} bash -c \""); # extracting from multiple backup levels backup_levels.append(target["dep"][0]) f.write("cd $$(basename $@ .tar);") lastincrement = None for increment in backup_levels[-1:]: if lastincrement == None: # level-0 backup f.write("[ -f ../%s.snar ]" % increment); f.write(" && tar -g ../%s.snar -xf ../%s" % (increment, increment)); f.write(" || tar -xf ../%s;" % increment); else: # level-x backup, update snar after extract f.write("tar -g ../%s.snar -xf ../%s;" % (increment, increment)); lastincrement = increment if not dry_run: f.write("if [ -f ../.trg.sh ]; then mv ../.trg.sh .; chroot . ./.trg.sh || exit 1; fi; rm -f ./.trg.sh;"); f.write("if [ -f ../.hst.sh ]; then ../.hst.sh || exit 1; fi; rm -f ../.hst.sh;"); if not 'temporary' in target or not target['temporary']: # copy over snar file from previous level and add changes to the new one f.write("[ ! -f ../$<.snar ] || cp ../$<.snar ../$@.snar;"); if incremental: f.write("tar -g ../$@.snar -cf '../$@' .;"); else: f.write("tar -cf '../$@' .;"); f.write("\"\n"); # ... end command if 'temporary' in target and target['temporary']: f.write("\t[ ! -f $< ] || cp $< $@;"); f.write("\t[ ! -f $<.snar ] || cp $<.snar $@.snar;\n"); f.write("\t${Q}-${SUDO} rm -Rf ./$$(basename $@ .tar)\n"); # # end of target/host commands. # we keep it open that those command types can still be combined # with a simple command. # if 'simplecmd' in target: # note, that a simple command has full control # over make. it can contain references to the maketarget # and to make variables. Therefore we don't replace anything # in those commands (contrary to target-/host commands). # Also verbose mode can't be supported, because we want to # support multiline commands for line in iter(target['simplecmd'].splitlines()): f.write("\t%s\n" % line) f.write("\n"); # write footer f.write("include %s/wharfie.mk\n" % installpath); if installpath != '.': f.write("-include wharfie.mk\n"); f.write("\n"); |