# # Copyright (C) 2023 Soleta Networks # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Affero General Public License as published by the # Free Software Foundation; either version 3 of the License, or # (at your option) any later version. import hashlib import logging import os import shlex import shutil import subprocess import urllib.request from src.log import OgError from src.utils.cache import * def _compute_md5(path, bs=2**20): m = hashlib.md5() with open(path, 'rb') as f: while True: buf = f.read(bs) if not buf: break m.update(buf) return m.hexdigest() def tip_fetch_csum(tip_addr, image_name): """ """ url = f'http://{tip_addr}:9999/{image_name}.img.full.sum' try: with urllib.request.urlopen(f'{url}') as resp: r = resp.readline().rstrip().decode('utf-8') except urllib.error.URLError as e: raise OgError(f'Error fetching checksum file from {url}: {e.reason}') from e except urllib.error.HTTPError as e: raise OgError(f'Error fetching checksum file via HTTP from {url}: {e.reason}') from e return r def _tip_read_csum(image_checksum_path): try: with open(image_checksum_path, 'r') as f: checksum = f.read().strip("\n") except OSError as e: return "unavailable" return checksum def tip_write_csum(image_name): if not mount_cache(): raise OgError(f'Failed to checksum {image_name}: cache partition is not available') image_path = f'{OG_CACHE_IMAGE_PATH}{image_name}.img' if not os.path.exists(image_path): raise OgError(f'Invalid image path {image_path} for tiptorrent checksum writing') logging.info('Calculating checksum...') logging.info('*DO NOT REBOOT OR POWEROFF* the client during this time') filename = image_path + ".full.sum" csum = _compute_md5(image_path) try: with open(filename, 'w') as f: f.write(csum) except OSError as e: raise OgError(f'Could not write {filename}, reported: {e}') from e return csum def tip_check_csum(tip_addr, image_name): """ """ logging.info(f'Verifying checksum for {image_name}.img, please wait...') image_path = f'{OG_CACHE_IMAGE_PATH}{image_name}.img' if not os.path.exists(image_path): logging.error(f'File {image_path} does not exist') return False if not os.path.exists(f"{image_path}.full.sum"): logging.error(f'File {image_path}.full.sum does not exist in repository {tip_addr}') return False cache_csum = _tip_read_csum(f"{image_path}.full.sum") remote_csum = tip_fetch_csum(tip_addr, image_name) if cache_csum == remote_csum: ret = True logging.info(f'Checksum is OK for {image_name}.img') else: ret = False logging.warning(f'Checksum mismatch for {image_name}.img') logging.warning(f'Server reports checksum {remote_csum} but local checksum is {cache_csum}') return ret def tip_client_get(tip_addr, image_name): """ """ image_path = f"{OG_CACHE_IMAGE_PATH}{image_name}.img" if os.path.exists(image_path): os.unlink(image_path) if os.path.exists(f"{image_path}.full.sum"): os.unlink(f"{image_path}.full.sum") logging.info(f'Fetching image {image_name}.img from tiptorrent server at {tip_addr}') logging.info('*DO NOT REBOOT OR POWEROFF* the client during this time') cmd = f'tiptorrent-client {tip_addr} {image_name}.img' logfile = open('/tmp/command.log', 'wb', 0) try: proc = subprocess.Popen(shlex.split(cmd), stdout=logfile, cwd=OG_CACHE_IMAGE_PATH) proc.communicate() except OSError as e: raise OgError(f'Unexpected error running tiptorrent subprocess: {e}') from e finally: logfile.close() if proc.returncode != 0: raise OgError(f'Error fetching image {image_name} via tiptorrent') else: tip_write_csum(image_name)