#!/usr/bin/env python3 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  json 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  lzma 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  hashlib 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  requests 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  struct 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  subprocess 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  os 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								from  common . spinner  import  Spinner 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								class  StreamingDecompressor : 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  __init__ ( self ,  url ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . buf  =  b " " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . req  =  requests . get ( url ,  stream = True ,  headers = { ' Accept-Encoding ' :  None } ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . it  =  self . req . iter_content ( chunk_size = 1024  *  1024 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . decompressor  =  lzma . LZMADecompressor ( format = lzma . FORMAT_AUTO ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . eof  =  False 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . sha256  =  hashlib . sha256 ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  read ( self ,  length ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    while  len ( self . buf )  <  length : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      self . req . raise_for_status ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      try : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        compressed  =  next ( self . it ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      except  StopIteration : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . eof  =  True 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        break 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      out  =  self . decompressor . decompress ( compressed ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      self . buf  + =  out 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    result  =  self . buf [ : length ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . buf  =  self . buf [ length : ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . sha256 . update ( result ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    return  result 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								def  unsparsify ( f ) : 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  magic  =  struct . unpack ( " I " ,  f . read ( 4 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  assert ( magic  ==  0xed26ff3a ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  # Version 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  major  =  struct . unpack ( " H " ,  f . read ( 2 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  minor  =  struct . unpack ( " H " ,  f . read ( 2 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  assert ( major  ==  1  and  minor  ==  0 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  # Header sizes 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  _  =  struct . unpack ( " H " ,  f . read ( 2 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  _  =  struct . unpack ( " H " ,  f . read ( 2 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  block_sz  =  struct . unpack ( " I " ,  f . read ( 4 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  _  =  struct . unpack ( " I " ,  f . read ( 4 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  num_chunks  =  struct . unpack ( " I " ,  f . read ( 4 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  _  =  struct . unpack ( " I " ,  f . read ( 4 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  for  _  in  range ( num_chunks ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    chunk_type  =  struct . unpack ( " H " ,  f . read ( 2 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    _  =  struct . unpack ( " H " ,  f . read ( 2 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    out_blocks  =  struct . unpack ( " I " ,  f . read ( 4 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    _  =  struct . unpack ( " I " ,  f . read ( 4 ) ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  chunk_type  ==  0xcac1 :   # Raw 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      # TODO: yield in smaller chunks. Yielding only block_sz is too slow. Largest observed data chunk is 252 MB. 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      yield  f . read ( out_blocks  *  block_sz ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    elif  chunk_type  ==  0xcac2 :   # Fill 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      filler  =  f . read ( 4 )  *  ( block_sz  / /  4 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      for  _  in  range ( out_blocks ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        yield  filler 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    elif  chunk_type  ==  0xcac3 :   # Don't care 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      yield  b " " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    else : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      raise  Exception ( " Unhandled sparse chunk type " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								def  flash_partition ( cloudlog ,  spinner ,  target_slot ,  partition ) : 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  cloudlog . info ( f " Downloading and writing  { partition [ ' name ' ] } " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  downloader  =  StreamingDecompressor ( partition [ ' url ' ] ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								  with  open ( f " /dev/disk/by-partlabel/ { partition [ ' name ' ] } { target_slot } " ,  ' wb+ ' )  as  out : 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    partition_size  =  partition [ ' size ' ] 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    # Check if partition is already flashed 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    out . seek ( partition_size ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  out . read ( 64 )  ==  partition [ ' hash_raw ' ] . lower ( ) . encode ( ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      cloudlog . info ( f " Already flashed  { partition [ ' name ' ] } " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      return 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    # Clear hash before flashing 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    out . seek ( partition_size ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    out . write ( b " \x00 "  *  64 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    out . seek ( 0 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    os . sync ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    # Flash partition 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  partition [ ' sparse ' ] : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      raw_hash  =  hashlib . sha256 ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      for  chunk  in  unsparsify ( downloader ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        raw_hash . update ( chunk ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        out . write ( chunk ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  spinner  is  not  None : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          spinner . update_progress ( out . tell ( ) ,  partition_size ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  raw_hash . hexdigest ( ) . lower ( )  !=  partition [ ' hash_raw ' ] . lower ( ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        raise  Exception ( f " Unsparse hash mismatch  ' { raw_hash . hexdigest ( ) . lower ( ) } ' " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    else : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      while  not  downloader . eof : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        out . write ( downloader . read ( 1024  *  1024 ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  spinner  is  not  None : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          spinner . update_progress ( out . tell ( ) ,  partition_size ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  downloader . sha256 . hexdigest ( ) . lower ( )  !=  partition [ ' hash ' ] . lower ( ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      raise  Exception ( " Uncompressed hash mismatch " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  out . tell ( )  !=  partition [ ' size ' ] : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      raise  Exception ( " Uncompressed size mismatch " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    # Write hash after successfull flash 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    os . sync ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    out . write ( partition [ ' hash_raw ' ] . lower ( ) . encode ( ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								def  flash_agnos_update ( manifest_path ,  cloudlog ,  spinner = None ) : 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  update  =  json . load ( open ( manifest_path ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  current_slot  =  subprocess . check_output ( [ " abctl " ,  " --boot_slot " ] ,  encoding = ' utf-8 ' ) . strip ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  target_slot  =  " _b "  if  current_slot  ==  " _a "  else  " _a " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  target_slot_number  =  " 0 "  if  target_slot  ==  " _a "  else  " 1 " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  cloudlog . info ( f " Current slot  { current_slot } , target slot  { target_slot } " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  # set target slot as unbootable 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  os . system ( f " abctl --set_unbootable  { target_slot_number } " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  for  partition  in  update : 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    success  =  False 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    for  retries  in  range ( 10 ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      try : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        flash_partition ( cloudlog ,  spinner ,  target_slot ,  partition ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        success  =  True 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        break 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      except  requests . exceptions . RequestException : 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								        cloudlog . exception ( " Failed " ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								        spinner . update ( " Waiting for internet... " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        cloudlog . info ( f " Failed to download  { partition [ ' name ' ] } , retrying ( { retries } ) " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        time . sleep ( 10 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  not  success : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      cloudlog . info ( f " Failed to flash  { partition [ ' name ' ] } , aborting " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      raise  Exception ( " Maximum retries exceeded " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  cloudlog . info ( f " AGNOS ready on slot  { target_slot } " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								if  __name__  ==  " __main__ " : 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  import  logging 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  import  time 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  import  sys 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  if  len ( sys . argv )  !=  2 : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    print ( " Usage: ./agnos.py <manifest.json> " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    exit ( 1 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  spinner  =  Spinner ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  spinner . update ( " Updating AGNOS " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  time . sleep ( 5 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  logging . basicConfig ( level = logging . INFO ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  flash_agnos_update ( sys . argv [ 1 ] ,  logging ,  spinner )