o
    w7e8T                  
   @   s>  U d Z ddlZddlZddlZddlZddlZddlZddlZddlm	Z	m
Z
 ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ dd	lmZmZmZ dd
lmZ ddlmZ ddlmZmZ ddl m!Z!m"Z" ddl#m$Z$ dZ%e$Z&ddde%e!ge&ededgg dZ'ee(d< ee'Z ddgddZ)edZ*G dd dZ+e,e-Z.de/de"d e0fd!d"Z1G d#d$ d$e2Z3G d%d& d&e	Z4G d'd( d(e4Z5G d)d* d*e4Z6G d+d, d,e4Z7d-d. Z8d/d0 Z9d1d2 Z:d3d4 Z;d5e<fd6d7Z=d8d9 Z>d5ee/e/f fd:d;Z?d<d= Z@d>e/d?ed@edAe0d5df
dBdCZAdDe5fdEe6fdFe7ffZBdS )GzGrowpart: Grow partitions    N)ABCabstractmethod)suppress)Path)dedent)Tuple)log)subp
temp_utilsutil)Cloud)Config)
MetaSchemaget_meta_doc)ALL_DISTROSDistro)
PER_ALWAYSa6  Growpart resizes partitions to fill the available disk space.
This is useful for cloud instances with a larger amount of disk space available
than the pristine image uses, as it allows the instance to automatically make
use of the extra space. Note that this only works if the partition to be
resized is the last one on a disk with classic partitioning scheme (MBR, BSD,
GPT). LVM, Btrfs and ZFS have no such restrictions.

The devices on which to run growpart are specified as a list under the
``devices`` key.

There is some functionality overlap between this module and the ``growroot``
functionality of ``cloud-initramfs-tools``. However, there are some situations
where one tool is able to function and the other is not. The default
configuration for both should work for most cloud instances. To explicitly
prevent ``cloud-initramfs-tools`` from running ``growroot``, the file
``/etc/growroot-disabled`` can be created. By default, both ``growroot`` and
``cc_growpart`` will check for the existence of this file and will not run if
it is present. However, this file can be ignored for ``cc_growpart`` by setting
``ignore_growroot_disabled`` to ``true``. For more information on
``cloud-initramfs-tools`` see: https://launchpad.net/cloud-initramfs-tools

On FreeBSD, there is also the ``growfs`` service, which has a lot of overlap
with ``cc_growpart`` and ``cc_resizefs``, but only works on the root partition.
In that configuration, we use it, otherwise, we fall back to ``gpart``.

Note however, that ``growfs`` may insert a swap partition, if none is present,
unless instructed not to via ``growfs_swap_size=0`` in either ``kenv(1)``, or
``rc.conf(5)``.

Growpart is enabled by default on the root partition. The default config for
growpart is::

    growpart:
      mode: auto
      devices: ["/"]
      ignore_growroot_disabled: false
cc_growpartGrowpartzGrow partitionsz            growpart:
              mode: auto
              devices: ["/"]
              ignore_growroot_disabled: false
            z            growpart:
              mode: growpart
              devices:
                - "/"
                - "/dev/vdb1"
              ignore_growroot_disabled: true
            )idnametitledescriptiondistros	frequencyexamplesactivate_by_schema_keysmetaauto/F)modedevicesignore_growroot_disabledz/cc_growpart_keydatac                   @   s   e Zd ZdZdZdZdZdS )RESIZESKIPPEDCHANGEDNOCHANGEFAILEDN)__name__
__module____qualname__r$   r%   r&   r'    r+   r+   >/usr/lib/python3/dist-packages/cloudinit/config/cc_growpart.pyr#   o   s
    r#   r    distror!   c                 C   s   d }| dkr#t D ]\}}||}|j|dr|} nq|s!td|S i }t D ]\}}	|	||< q'| |vr:td|  ||  |}
|
j|drH|
}|sPtd|  |S )Nr   )r!   zNo resizers availablezunknown resize mode %szmode %s not available)RESIZERS	available
ValueError	TypeError)r    r-   r!   resize_class_nameresizercurmmapkvmclassr+   r+   r,   resizer_factoryy   s,   
r:   c                   @   s   e Zd ZdS )ResizeFailedExceptionN)r(   r)   r*   r+   r+   r+   r,   r;      s    r;   c                   @   s<   e Zd ZdefddZededefddZedd	 Z	d
S )Resizerr-   c                 C   s
   || _ d S N)_distro)selfr-   r+   r+   r,   __init__   s   
zResizer.__init__r!   returnc                 C      d S r=   r+   r?   r!   r+   r+   r,   r/         zResizer.availablec                 C   rB   r=   r+   )r?   diskdevpartnumpartdevr+   r+   r,   resize   rD   zResizer.resizeN)
r(   r)   r*   r   r@   r   listboolr/   rH   r+   r+   r+   r,   r<      s    r<   c                   @   "   e Zd ZdefddZdd ZdS )ResizeGrowPartr!   c                 C   sX   t j }d|d< ztjddg|d\}}td|rW dS W dS  tjy+   Y dS w )	NCLANGgrowpartz--helpenvz--update\s+TFosenvironcopyr	   researchProcessExecutionError)r?   r!   myenvout_errr+   r+   r,   r/      s   
zResizeGrowPart.availablec           
      C   sV  t j }d|d< t|}| j }tj|dd}t j	|d}t j
|s-t |d ||d< ztjdd||g|d	 W n0 tjyn }	 z#|	jd
krYttd|| t|	|	||fW  Y d }	~	W  d    S d }	~	ww ztjd||g|d	 W n tjy }	 zttd|| t|	|	d }	~	ww W d    n1 sw   Y  |t|fS )NrM   rN   T)dir	needs_exerO   i  TMPDIRz	--dry-runrP      z&Failed growpart --dry-run for (%s, %s)zFailed: growpart %s %s)rS   rT   rU   get_sizer>   get_tmp_exec_pathr
   tempdirpathjoinexistsmkdirr	   rX   	exit_coder   logexcLOGr;   )
r?   rE   rF   rG   rY   beforetmp_dirtmpdgrowpart_tmper+   r+   r,   rH      sH   




	
zResizeGrowPart.resizeNr(   r)   r*   rI   r/   rH   r+   r+   r+   r,   rL          rL   c                   @   s&   e Zd ZdZdefddZdd ZdS )ResizeGrowFSa  
    Use FreeBSD ``growfs`` service to grow root partition to fill available
    space, optionally adding a swap partition at the end.

    Note that the service file warns us that it uses ``awk(1)``, and as
    such requires ``/usr`` to be present. However, cloud-init is installed
    into ``/usr/local``, so we should be fine.

    We invoke the ``growfs`` with ``service growfs onestart``, so it
    doesn't need to be enabled in ``rc.conf``.
    r!   c                 C   s   t jdo
|dgkS )z'growfs only works on the root partitionz/etc/rc.d/growfsr   )rS   rc   isfilerC   r+   r+   r,   r/      s   zResizeGrowFS.availablec              
   C   sZ   t |}z
| jjddd W n tjy& } zttd t||d }~ww |t |fS )Nonestartgrowfs)actionservicezFailed: service growfs onestart)	r`   r>   manage_servicer	   rX   r   rh   ri   r;   )r?   rE   rF   rG   rj   rn   r+   r+   r,   rH      s   
zResizeGrowFS.resizeN)r(   r)   r*   __doc__rI   r/   rH   r+   r+   r+   r,   rq      s    rq   c                   @   rK   )ResizeGpartr!   c                 C   s^   t j }d|d< ztjddg|ddgd\}}td|r!W d	S W d
S  tjy.   Y d
S w )NrM   rN   gparthelpr   r_   )rQ   rcszgpart recover TFrR   )r?   r!   rY   _outerrr+   r+   r,   r/      s   
zResizeGpart.availablec              
   C   s   z
t  dd|g W n$ t jy. } z|jdkr$ttd| t||W Y d}~nd}~ww t|}zt  ddd||g W n t jyY } zttd|| t||d}~ww |t|fS )	a9  
        GPT disks store metadata at the beginning (primary) and at the
        end (secondary) of the disk. When launching an image with a
        larger disk compared to the original image, the secondary copy
        is lost. Thus, the metadata will be marked CORRUPT, and need to
        be recovered.
        rz   recoverr   zFailed: gpart recover %sNrH   z-izFailed: gpart resize -i %s %s)r	   rX   rg   r   rh   ri   r;   r`   )r?   rE   rF   rG   rn   rj   r+   r+   r,   rH     s$   


zResizeGpart.resizeNro   r+   r+   r+   r,   ry      rp   ry   c              	   C   s8   t | t j}zt |dt jW t | S t | w )Nr   )rS   openO_RDONLYlseekSEEK_ENDclose)filenamefdr+   r+   r,   r`     s   r`   c                 C   s   t j| }t j|}d| }t r+dt|  }td|}|r+|d |d fS t j	|s9t
d| |f t j|d}t j	|sLtd|  t| }t j|}t j|}	tt j|	d }
t jd	|
 }||fS )
Nz/sys/class/block/%s/dev/z/^(?P<dev>/dev/.+)[sp](?P<part_slice>\d+[a-z]*)$dev
part_slicez%s had no syspath (%s)	partitionz%s not a partitionz/dev/block/%s)rS   rc   realpathbasenamer   is_BSDfind_freebsd_partrV   rW   re   r0   rd   r1   	load_filerstripdirname)devpathrpathbnamesyspathfpartmptpathptnumrsyspathdisksyspath
diskmajmindiskdevpathr+   r+   r,   device_part_info&  s*   r   c                 C   sr   |  dr| S t| }|std|d }t }|dkr7|s7tt }|d u r7tj	|r3|S td|S )Nr   z,Could not determine device of '%s' % dev_entr   z	/dev/rootz!Unable to find device '/dev/root')

startswithr   get_mount_infor0   is_containerrootdev_from_cmdlineget_cmdlinerS   rc   re   )deventresultr   	containerr+   r+   r,   
devent2devQ  s   

r   c                 C   s,   t j| }|drtd| | |S dS )a  Returns underlying block device for a mapped device.

    If it is mapped, blockdev will usually take the form of
    /dev/mapper/some_name

    If blockdev is a symlink pointing to a /dev/dm-* device, return
    the device pointed to. Otherwise, return None.
    z/dev/dm-z$%s is a mapped device pointing to %sN)rS   rc   r   r   ri   debug)blockdevr   r+   r+   r,   get_mapped_deviceh  s
   	
r   rA   c              
   C   s   t dstd dS z
t  dd| g W n' t jy= } z|jdkr+td|  ntd|j W Y d}~dS d}~ww tt j t  dd	|g td
|  	 W d   dS 1 s^w   Y  dS )z
    Check if a device is an encrypted device. blockdev should have
    a /dev/dm-* path whereas partition is something like /dev/sda1.
    
cryptsetupz6cryptsetup not found. Assuming no encrypted partitionsFstatus   z#Determined that %s is not encryptedzZReceived unexpected exit code %s from cryptsetup status. Assuming no encrypted partitions.NisLukszDetermined that %s is encryptedT)r	   whichri   r   rX   rg   warningr   )r   r   rn   r+   r+   r,   is_encryptedx  s*   



 r   c              
   C   s   ddd| g}t  |d }|dstd| zd|dd	 d
d  W S  tyA } ztd| d| d|d }~ww )Ndmsetupdepsz--options=devnamer   z1 dependz5Expecting '1 dependencies' from 'dmsetup'. Received: r   z: (r_   )zRan `z$`, but received unexpected stdout: ``)r	   r   RuntimeErrorsplit
IndexError)r   commanddeprn   r+   r+   r,   get_underlying_partition  s    
 r   c                 C   s  t  s	tjdfS z(t  }t|}W d   n1 sw   Y  |d }t|}|d }W n t	yB } zt
d|d}~ww zKtjdddd	| g|d
 W ztddd|t|g W n tjyx } ztd| W Y d}~nd}~ww zt   W nM t	y   ttd Y n?w ztddd|t|g W n tjy } ztd| W Y d}~nd}~ww zt   W w  t	y   ttd Y w w tjd|  dfS )zUse 'cryptsetup resize' to resize LUKS volume.

    The loaded keyfile is json formatted with 'key' and 'slot' keys.
    key is base64 encoded. Example:
    {"key":"XFmCwX2FHIQp0LBWaLEMiHIyfxt1SGm16VvUAVledlY=","slot":5}
    zNo encryption keyfile foundNkeyslotzZCould not load encryption key. This is expected if the volume has been previously resized.r   z
--key-file-rH   )dataluksKillSlotz--batch-modez<Failed to kill luks slot after resizing encrypted volume: %sz8Failed to remove keyfile after resizing encrypted volumez'Successfully resized encrypted volume '')KEYDATA_PATHre   r#   r$   r   jsonloadbase64	b64decode	Exceptionr   r	   strrX   ri   r   unlinkr   rh   r%   )r   r   fkeydatar   decoded_keyr   rn   r+   r+   r,   resize_encrypted  s   


		
r   c                 C   s  t  |}g }|rV|d}zt|}W n ty2 } z||tjd| f W Y d }~qd }~ww zt|}W n t	yY } z||tjd||f f W Y d }~qd }~ww t
|jsrt|jsr||tjd| f qt|}|rz?t|}t||r|dd |D vr|d| |d| W qt||\}	}
|||	|
f n||tjd| df W n! ty } z||tjd	| d
| f W Y d }~nd }~ww qzt|\}}W n" ttfy } z||tjd||f f W Y d }~qd }~ww z-| |||\}}||kr"||tjd||f f n||tjd||||f f W n! tyS } z||tjd|||f f W Y d }~nd }~ww |s
|S )Nr   zunable to convert to device: %szstat of '%s' failed: %szdevice '%s' not a block devicec                 S   s   g | ]}|d  qS )r   r+   ).0xr+   r+   r,   
<listcomp>  s    z"resize_devices.<locals>.<listcomp>zResizing mapped device (z!) skipped as it is not encrypted.zResizing encrypted device (z
) failed: zdevice_part_info(%s) failed: %szno change necessary (%s, %s)zchanged (%s, %s) from %s to %sz'failed to resize: disk=%s, ptnum=%s: %s)rU   popr   r0   appendr#   r$   rS   statOSErrorS_ISBLKst_modeS_ISCHRr   r   r   insertr   r   r'   r   r1   rH   r&   r%   r;   )r4   r!   infor   r   rn   statretunderlying_blockdevr   r   messagediskr   oldnewr+   r+   r,   resize_devices  s   












	{r   r   cfgcloudargsc              
   C   s  d|vrt dt t|d< |d}t|tst d d S |dd}t|r>|dkr6tj	ddd	d
 t d| d S t|ddrYt
jdrYt d t d d S t|ddg}t|slt d d S z
t||j|d}W n" ttfy } zt d|| |dkr|W Y d }~d S d }~ww tjt jdt||fd}	|	D ]\}
}}|tjkrt d|
| qt d|
|| qd S )NrO   z.No 'growpart' entry in cfg.  Using default: %sz#'growpart' in config was not a dictr    r   offz)Growpart's 'mode' key with value '{mode}'z22.2zUse 'off' instead.)
deprecateddeprecated_versionextra_messagezgrowpart disabled: mode=%sr"   Fz/etc/growroot-disabledz0growpart disabled: /etc/growroot-disabled existsz&use ignore_growroot_disabled to ignorer!   r   zgrowpart: empty device list)r-   r!   z,growpart unable to find resizer for '%s': %sr   )logfuncmsgfuncr   z'%s' resized: %sz'%s' %s: %s)ri   r   DEFAULT_CONFIGget
isinstancedictr   r   is_false	deprecaterS   rc   rr   get_cfg_option_listlenr:   r-   r0   r1   log_timer   r#   r%   r   )r   r   r   r   mycfgr    r!   r4   rn   resizedentryru   r   r+   r+   r,   handle^  s`   







r   rO   rt   rz   )Crx   r   rU   r   rS   os.pathrV   r   abcr   r   
contextlibr   pathlibr   textwrapr   typingr   	cloudinitr   loggingr	   r
   r   cloudinit.cloudr   cloudinit.configr   cloudinit.config.schemar   r   cloudinit.distrosr   r   cloudinit.settingsr   MODULE_DESCRIPTIONr   r   __annotations__r   r   r#   	getLoggerr(   ri   r   rI   r:   r   r;   r<   rL   rq   ry   r`   r   r   r   rJ   r   r   r   r   r   r.   r+   r+   r+   r,   <module>   s   &
3'+7 8