# -*- coding: utf-8 -*-
import time
import os
import sys
import yaml
import pyinotify
import logging
import shlex
from math import floor
import multiprocessing
from multiprocessing import Queue
import asyncore
import configparser
import random
import string
import shutil
import subprocess
import hashlib

# Create a custom logger
log = logging.getLogger('DIODE_OUT')
log.setLevel(logging.INFO)

# Create handlers
# Console handler
c_handler = logging.StreamHandler()
c_handler.setLevel(logging.INFO)
# file handler
log_file = os.path.join(os.getcwd(), "LOG/sender.log")
f_handler = logging.FileHandler(log_file)
f_handler.setLevel(logging.INFO)

# Create formatters and add it to handlers
c_format = logging.Formatter('%(asctime)s - %(funcName)s - %(levelname)s - %(message)s')
f_format = logging.Formatter('%(asctime)s - %(funcName)s - %(levelname)s - %(message)s')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)

# Add handlers to the logger
log.addHandler(c_handler)
log.addHandler(f_handler)


def launch_agents(module, properties):
    try:     
        if properties['type'] == 'folder':
            log.debug('Instanciating a file transfer module :: %s' % module)
            file_reception_loop(properties)
    except Exception:
        log.error("Can not launch process %s" % module, exc_info=True)

def list_all_files(dir):
    """
    List all files recursively
    """
    files = []
    try:
        for root, directories, filenames in os.walk(dir):
            for filename in filenames:
                files.append(os.path.join(root,filename))
        return files
    except Exception:
        log.error("List files failed.", exc_info=True)
        return

def hash_file(file):
    BLOCKSIZE = 65536
    hasher = hashlib.sha256()
    try:
        with open(file, 'rb') as afile:
            buf = afile.read(BLOCKSIZE)
            while len(buf) > 0:
                hasher.update(buf)
                buf = afile.read(BLOCKSIZE)
        
        return hasher.hexdigest()
    except Exception:
        log.error("Error calculate hash", exc_info=True)
        return 

def check_hash_process(queue, params):
    while True:
        # Block until an item is avalable
        temp_file, hash_list, failure_log = queue.get()
        if hash_list is None:
            if temp_file is None:
                break # reached end of queue
        echo = subprocess.Popen(['echo','123456'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        p = subprocess.Popen(shlex.split('sudo -S chmod 777 ' + temp_file), shell=False, stdin=echo.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        h = hash_file(temp_file)
        # If hash_values haven't in manifest_file, error
        if h not in hash_list:
            log.error("Invalid checksum for file " + temp_file + " ::: " + h)
            with open(failure_log, 'a') as f:
                f.write(h + ' ::: ' + temp_file + '\n')
                os.remove(temp_file)
        # if hash is match
        else:
            f = hash_list[h]
            #file_name = os.path.basename(f)
            file_name = os.path.relpath(f, config['receiver']['sourcepath'])
            try:
                # Move to OUT folder
                shutil.move(temp_file, params['out'] + '/' + file_name)
            except Exception:
                log.error("Error move file", exc_info=True)
                # Do not return here
            log.info('File Available: ' + file_name)
    queue.put(None) # add end-of-queue markers, shutdown

######################## Reception funcitons ##########################

def parse_manifest(file):
    """
    Parse information receive from Tx
    """
    parser = configparser.SafeConfigParser()
    parser.read(file)
    files = {}
    try:
        for item, value in parser.items('Files'):
            log.debug(item + ' :: ' + value)
            files[item] = value
        return files
    except:
        return files

# File reception forever loop
def file_reception_loop(params):
    # background hash check
    queue = Queue() 

    hash_p = multiprocessing.Process(target=check_hash_process, args=(queue, params))
    hash_p.daemon = True
    hash_p.start()

    while True:
        wait_for_file(queue, params)
    
    queue.put((None,))
    hash_p.join()
    if hash_p.is_alive():
        hash_p.terminate()

def receive_file(filepath, portbase):
    """
    Launch UDPcast to receive a file
    """
    #log.debug(portbase)
    command = 'udp-receiver --nosync --mcast-rdv-addr ' + str(config['sender']['ip']) + ' --interface ' + str(config['receiver']['interface']) \
        + ' --portbase ' + str(portbase) + ' -f ' + "\"" + filepath + "\""
    log.debug(command)
    try:
        p = subprocess.Popen(shlex.split(command), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output, err = p.communicate()
    except Exception:
        log.error("Error when receive file %s" % filepath, exc_info=True)

def wait_for_file(queue, params):
    """
    File reception function
    """
    log.debug(params)
    # Use a dedicated name for each process to prevent race conditions
    # Transfer file, modbus, screen
    process_name = multiprocessing.current_process().name
    try:
        if not os.path.exists(params['temp']):
            os.mkdir(params['temp'])
    except Exception:
        log.error("Error create tmp fodler", exc_info=True)
        return 0
    manifest_filename = params['temp'] + '/manifest_' + process_name +'.cfg'
    try:
        receive_file(manifest_filename, params['port'] + 2)
        files = parse_manifest(manifest_filename)
    except Exception:
        log.error("Error receive manifest file", exc_info=True)
        # Get out and wait new manifest file
        return 0
    if len(files) == 0:
        log.debug('No file listed in manifest')
        # Get out
        return 0
    log.debug('Manifest content : %s' % files)

    hash_list = dict()
    try: 
        for f, h in files.items():
            hash_list[h] = f
    except Exception:
        log.error("Error reverse dictionary", exc_info=True)
        return 0

    for f, h in files.items():
        #filename = os.path.basename(f)
        temp_file = params['temp'] + '/TMP12345' + ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(12))
        try:
            receive_file(temp_file, params['port'])
        except:
            log.error("Error receive data file : %s" % f)
            # Get out and wait new manifest file
            return 0
        log.info('File received: ' + temp_file)
        if os.path.exists(temp_file):
            queue.put((temp_file, hash_list,params['LOG'] + '/transfer_failure.log'))
        
    try:
        os.remove(manifest_filename)
    except Exception:
        log.error("Can't delete. File not found", exc_info=True)
        return 0
    # Delete files in /tmp folder
    #queue.put((params['temp'], None, None))

if __name__ == '__main__':
    with open('config.yaml', 'r') as config_file:
        config = yaml.safe_load(config_file)

    # Clear folder OUT and manifest_file to safety start
    try:
        #redundancy_files = list_all_files(config['receiver']['FTPout'])
        #for file in redundancy_files:
        #    os.remove(file)
        tmp_files = list_all_files(config['receiver']['temp'])
        for file in tmp_files:
            os.remove(file)
        log.info('Delete files from old session done !')
    except:
        log.error('Can not delete file from previous session')
    
    # Log infos about the configuration file
    log.info('Load config file')
    log.debug('Configuration name : ' + config['config_name'])
    log.debug('Configuration version : ' + str(config['config_version']))
    log.debug('Configuration date : ' + str(config['config_date']))

    # IP, MAC
    log.debug('Sender IP : %s (%s)' % (config['sender']['ip'], config['sender']['mac']))
    log.debug('Receiver IP : %s (%s)' % (config['receiver']['ip'], config['receiver']['mac']))

    # Setting Route
    echo = subprocess.Popen(['echo','123456'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    p = subprocess.Popen(shlex.split('sudo -S ip route add ' + config['routes']['SRC_NET'] + ' via ' + config['sender']['ip'] + \
        ' dev ' + config['receiver']['interface']), shell=False, stdin=echo.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, err = p.communicate()
    # Check if there is an errror
    if err == b'':
        log.info('Setup Route successfully!')
    else:
        log.error('Could not add Route: %s' % err)

    # Iterate on modules
    modules = config.get('modules')
    for module, properties in modules.items():
        log.debug('Parsing %s' % module)
        log.debug('Trying to lanch a new process for module %s' % module)
        try:
            p = multiprocessing.Process(name=str(module), target=launch_agents, args=(module, properties))
            p.start()
        except KeyboardInterrupt:
            log.info("Program terminated by keyboadInterrupt")
        except Exception:
            log.error("Can not launch process %s" % module, exc_info=True)

该程序名为receiver.py，正在Ubuntu系统中运行，运行方式为“python3 receiver.py”，普通用户权限，如果可以在其目录下写入任意文件，如何造成代码立即执行或者程序崩溃？