kvpm-0.9.10/0000755000175000017500000000000012770324542013010 5ustar benscottbenscottkvpm-0.9.10/kvpm.10000644000175000017500000000236612770324126014054 0ustar benscottbenscott.TH KVPM "1" "March 2012" KDE "KDE Application" .SH NAME kvpm \- Graphical user interface for LVM based on KDE .SH SYNOPSIS .B kvpm [\fIQt-options\fR] [\fIKDE-options\fR] .SH DESCRIPTION This manual page briefly documents the .B KVPM KDE Application. .P .B KVPM is a graphical user interface for the Linux Volume Manager (LVM) and libparted. It uses the standard LVM tools and programs to manipulate logical volumes, such as resizing, deleting or creating them. It can also format volumes and mount or unmount them. Creating, deleting and resizing partitions is also supported. .SS "Generic options:" .TP \fB\-\-help\fR Show help about options .TP \fB\-\-help\-qt\fR Show Qt specific options .TP \fB\-\-help\-kde\fR Show KDE specific options .TP \fB\-\-help\-all\fR Show all options .TP \fB\-\-author\fR Show author information .TP \fB\-v\fR, \fB\-\-version\fR Show version information .TP \fB\-\-license\fR Show license information .TP \fB\-\-\fR End of options .SH SEE ALSO Full user documentation is available through the KDE Help Center. You can also enter the URL .BR help:/kvpm/ directly into konqueror or you can run .BR "`khelpcenter help:/kvpm/'" from the command-line. .br .SH AUTHOR KVPM is copyright by .nh Benjamin Scott .hy .br kvpm-0.9.10/po/0000755000175000017500000000000012770324126013424 5ustar benscottbenscottkvpm-0.9.10/po/kvpm.pot0000644000175000017500000016742312770324126015142 0ustar benscottbenscott# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-09-13 19:32-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: allocationpolicy.cpp:34 allocationpolicy.cpp:58 msgid "normal" msgstr "" #: allocationpolicy.cpp:37 msgid "contiguous" msgstr "" #: allocationpolicy.cpp:40 msgid "cling" msgstr "" #: allocationpolicy.cpp:43 msgid "anywhere" msgstr "" #: allocationpolicy.cpp:46 msgid "inherit (normal)" msgstr "" #: allocationpolicy.cpp:49 msgid "inherit (contiguous)" msgstr "" #: allocationpolicy.cpp:52 msgid "inherit (cling)" msgstr "" #: allocationpolicy.cpp:55 msgid "inherit (anywhere)" msgstr "" #: allocationpolicy.cpp:94 msgid "Allocation policy: " msgstr "" #: allocationpolicy.cpp:97 msgid "Normal" msgstr "" #: allocationpolicy.cpp:98 msgid "Contiguous" msgstr "" #: allocationpolicy.cpp:99 msgid "Cling" msgstr "" #: allocationpolicy.cpp:100 msgid "Anywhere" msgstr "" #: allocationpolicy.cpp:103 msgid "Inherit (%1)" msgstr "" #: changemirror.cpp:63 msgid "Change Mirror" msgstr "" #: changemirror.cpp:67 msgid "This type of volume can not be mirrored " msgstr "" #: changemirror.cpp:70 msgid "RAID mirrors must be synced before adding new legs" msgstr "" #: changemirror.cpp:73 msgid "Non-RAID mirrors which are snapshot origins can not have new legs added" msgstr "" #: changemirror.cpp:80 msgid "Change mirror log: %1" msgstr "" #: changemirror.cpp:82 msgid "Change LVM mirror: %1" msgstr "" #: changemirror.cpp:84 msgid "Change RAID 1 mirror: %1" msgstr "" #: changemirror.cpp:86 msgid "Convert to a mirror: %1" msgstr "" #: changemirror.cpp:97 msgctxt "Common user options" msgid "General" msgstr "" #: changemirror.cpp:101 lvcreatebase.cpp:608 msgid "Physical layout" msgstr "" #: changemirror.cpp:133 msgid "No physical volumes suitable for a new mirror image found" msgstr "" #: changemirror.cpp:146 msgid "Standard LVM" msgstr "" #: changemirror.cpp:147 msgid "RAID 1" msgstr "" #: changemirror.cpp:159 msgid "Mirror type: " msgstr "" #: changemirror.cpp:171 msgid "Existing mirror legs: %1" msgstr "" #: changemirror.cpp:177 msgid "Add mirror legs: " msgstr "" #: changemirror.cpp:192 msgid "Add mirror legs" msgstr "" #: changemirror.cpp:196 msgid "Convert to mirror" msgstr "" #: changemirror.cpp:211 msgid "Mirror logging" msgstr "" #: changemirror.cpp:213 lvcreate.cpp:352 msgid "Memory based log" msgstr "" #: changemirror.cpp:214 lvcreate.cpp:351 msgid "Single disk based log" msgstr "" #: changemirror.cpp:215 lvcreate.cpp:350 msgid "Mirrored disk based log" msgstr "" #: changemirror.cpp:267 msgid "No change" msgstr "" #: changemirror.cpp:274 msgid "Remove both disk logs" msgstr "" #: changemirror.cpp:333 msgid "Volume striping" msgstr "" #: changemirror.cpp:346 lvcreate.cpp:262 msgid "Stripe Size: " msgstr "" #: changemirror.cpp:349 lvcreate.cpp:268 lvcreate.cpp:570 lvcreate.cpp:578 #: lvcreate.cpp:601 lvcreate.cpp:624 lvcreate.cpp:632 lvcreate.cpp:655 #: lvcreate.cpp:677 msgid "none" msgstr "" #: changemirror.cpp:355 lvcreate.cpp:274 msgid "Number of stripes: " msgstr "" #: changemirror.cpp:373 msgid "" "The number of extents: %1 must be evenly divisible by the number of stripes" msgstr "" #: deviceactions.cpp:56 deviceactions.cpp:58 msgid "Move or resize disk partition" msgstr "" #: deviceactions.cpp:57 msgid "change" msgstr "" #: deviceactions.cpp:62 deviceactions.cpp:64 msgid "Change partition flags" msgstr "" #: deviceactions.cpp:63 msgid "flags" msgstr "" #: deviceactions.cpp:68 deviceactions.cpp:69 deviceactions.cpp:70 msgid "Extend physical volume to fill device" msgstr "" #: deviceactions.cpp:74 deviceactions.cpp:76 msgid "Add disk partition" msgstr "" #: deviceactions.cpp:75 vgsplit.cpp:176 msgid "Add" msgstr "" #: deviceactions.cpp:80 deviceactions.cpp:82 msgid "Delete disk partition" msgstr "" #: deviceactions.cpp:81 lvactions.cpp:122 vgactions.cpp:79 msgid "Delete" msgstr "" #: deviceactions.cpp:86 deviceactions.cpp:88 vgcreate.cpp:235 msgid "Create volume group" msgstr "" #: deviceactions.cpp:87 msgid "New vg" msgstr "" #: deviceactions.cpp:92 deviceactions.cpp:94 msgid "Create or remove a partition table" msgstr "" #: deviceactions.cpp:93 msgid "Table" msgstr "" #: deviceactions.cpp:98 deviceactions.cpp:99 deviceactions.cpp:100 #: pvactions.cpp:45 msgid "Remove from volume group" msgstr "" #: deviceactions.cpp:104 lvactions.cpp:127 msgid "Make or remove filesystem..." msgstr "" #: deviceactions.cpp:105 lvactions.cpp:128 msgid "mkfs" msgstr "" #: deviceactions.cpp:106 lvactions.cpp:129 msgid "Make or remove a filesystem" msgstr "" #: deviceactions.cpp:109 lvactions.cpp:132 msgid "Extend filesystem to fill volume..." msgstr "" #: deviceactions.cpp:111 lvactions.cpp:134 msgid "Extend fs" msgstr "" #: deviceactions.cpp:112 lvactions.cpp:135 msgid "Extend filesystem to fill volume" msgstr "" #: deviceactions.cpp:115 lvactions.cpp:138 msgid "Run 'fsck -fp' on filesystem..." msgstr "" #: deviceactions.cpp:117 lvactions.cpp:140 msgid "fsck -fp" msgstr "" #: deviceactions.cpp:118 lvactions.cpp:141 msgid "Run 'fsck -fp' on the filesystem" msgstr "" #: deviceactions.cpp:121 lvactions.cpp:144 msgid "Mount filesystem..." msgstr "" #: deviceactions.cpp:122 lvactions.cpp:145 msgid "mount fs" msgstr "" #: deviceactions.cpp:123 lvactions.cpp:146 msgid "Mount filesystem" msgstr "" #: deviceactions.cpp:127 lvactions.cpp:150 msgid "Unmount filesystem..." msgstr "" #: deviceactions.cpp:128 lvactions.cpp:151 msgid "Unmount fs" msgstr "" #: deviceactions.cpp:129 lvactions.cpp:152 msgid "Unmount filesystem" msgstr "" #: devicemenu.cpp:31 lvactionsmenu.cpp:57 msgid "Filesystem operations" msgstr "" #: devicemenu.cpp:32 msgid "Extend volume group" msgstr "" #: deviceproperties.cpp:95 msgid "First sector: %1" msgstr "" #: deviceproperties.cpp:98 msgid "First aligned: %1 (to 1 MiB)" msgstr "" #: deviceproperties.cpp:101 msgid "Last sector: %1" msgstr "" #: deviceproperties.cpp:106 msgid "Flags: %1" msgstr "" #: deviceproperties.cpp:126 lvproperties.cpp:102 msgid "Mount Point" msgstr "" #: deviceproperties.cpp:128 lvproperties.cpp:98 msgid "Mount Points" msgstr "" #: deviceproperties.cpp:149 msgid "Not mounted" msgstr "" #: deviceproperties.cpp:171 msgid "Partition table: %1" msgstr "" #: deviceproperties.cpp:173 msgid "Logical sector size: %1" msgstr "" #: deviceproperties.cpp:174 msgid "Physical sector size: %1" msgstr "" #: deviceproperties.cpp:175 msgid "Sectors: %1" msgstr "" #: deviceproperties.cpp:178 msgctxt "May be read and not written" msgid "Read only" msgstr "" #: deviceproperties.cpp:180 msgid "Read/write" msgstr "" #: deviceproperties.cpp:183 msgid "Busy: Yes" msgstr "" #: deviceproperties.cpp:185 msgid "Busy: No" msgstr "" #: deviceproperties.cpp:201 lvproperties.cpp:169 msgid "Filesystem Label" msgstr "" #: deviceproperties.cpp:210 lvproperties.cpp:179 msgid "Filesystem UUID" msgstr "" #: deviceproperties.cpp:232 msgid "Physical Volume" msgstr "" #: deviceproperties.cpp:237 msgid "State: active" msgstr "" #: deviceproperties.cpp:239 msgid "State: inactive" msgstr "" #: deviceproperties.cpp:242 msgid "UUID" msgstr "" #: deviceproperties.cpp:263 msgid "Hardware" msgstr "" #: devicesizechartseg.cpp:136 msgid "Device: %1" msgstr "" #: devicetree.cpp:45 msgid "The device name" msgstr "" #: devicetree.cpp:46 msgid "The type of partition or device" msgstr "" #: devicetree.cpp:47 msgid "The amount of storage space" msgstr "" #: devicetree.cpp:48 msgid "The remaining storage space" msgstr "" #: devicetree.cpp:49 msgid "How the device is being used" msgstr "" #: devicetree.cpp:50 msgid "The Name of the volume group if the device is a physical volume" msgstr "" #: devicetree.cpp:51 msgid "Any flags associated with device" msgstr "" #: devicetree.cpp:52 msgid "Mount points of the filesystem if it is mounted" msgstr "" #: devicetree.cpp:482 msgid "A device mapper RAID volume" msgstr "" #: devicetree.cpp:484 msgid "A multiple device RAID volume" msgstr "" #: devicetree.cpp:487 msgid "A block device under a device mapper RAID volume" msgstr "" #: devicetree.cpp:489 msgid "A block device under a multiple device RAID volume" msgstr "" #: devicetree.cpp:493 pvtree.cpp:149 pvtree.cpp:151 msgid "Physical volume that is running out of space" msgstr "" #: devicetree.cpp:498 msgid "Physical volume with active logical volumes" msgstr "" #: devicetree.cpp:501 msgid "Physical volume without active logical volumes" msgstr "" #: devicetree.cpp:512 msgid "Filesystem that is running out of space" msgstr "" #: devicetree.cpp:516 vgtree.cpp:262 msgid "mounted filesystem" msgstr "" #: devicetree.cpp:519 vgtree.cpp:273 msgid "unmounted filesystem" msgstr "" #: devicetree.cpp:524 vgtree.cpp:266 msgid "Active swap area" msgstr "" #: devicetree.cpp:527 vgtree.cpp:269 msgid "Inactive swap area" msgstr "" #: fsck.cpp:48 msgid "Run 'fsck -fp' to check the filesystem on volume %1?" msgstr "" #: fsck.cpp:67 msgid "Run 'fsck -fp' to check the filesystem on partition %1?" msgstr "" #: fsextend.cpp:61 msgid "Executable: '%1' not found, this filesystem cannot be extended" msgstr "" #: fsextend.cpp:129 msgid "Only logical volumes may be mounted during resize, not partitions." msgstr "" #: fsextend.cpp:132 msgid "Filesystem '%1' can not be extended while mounted." msgstr "" #: fsextend.cpp:137 msgid "" "It appears that the filesystem on the device or volume was not extended. It " "will need to be extended before the additional space can be used." msgstr "" #: fsreduce.cpp:35 msgid "Executable: '%1' not found, this filesystem cannot be reduced" msgstr "" #: fsreduce.cpp:60 msgid "Shrink failed: could not determine filesystem block size" msgstr "" #: fsreduce.cpp:99 msgid "Now trying to shrink the filesystem to its minimum size" msgstr "" #: kvpmconfigdialog.cpp:55 lvcreatebase.cpp:82 msgctxt "The standard common options" msgid "General" msgstr "" #: kvpmconfigdialog.cpp:56 msgid "Colors" msgstr "" #: kvpmconfigdialog.cpp:57 msgid "Programs" msgstr "" #: kvpmconfigdialog.cpp:68 msgid "Tree Views" msgstr "" #: kvpmconfigdialog.cpp:69 msgid "Property Panels" msgstr "" #: kvpmconfigdialog.cpp:78 msgid "Set columns to show in tables and tree views" msgstr "" #: kvpmconfigdialog.cpp:98 msgid "Set information to show in property panels" msgstr "" #: kvpmconfigdialog.cpp:118 msgid "Program name" msgstr "" #: kvpmconfigdialog.cpp:118 msgid "Full path" msgstr "" #: kvpmconfigdialog.cpp:143 msgid "Set the search path for support programs" msgstr "" #: kvpmconfigdialog.cpp:216 msgid "Device Tree" msgstr "" #: kvpmconfigdialog.cpp:241 msgid "Device name" msgstr "" #: kvpmconfigdialog.cpp:242 msgid "Partition type" msgstr "" #: kvpmconfigdialog.cpp:243 msgid "Capacity" msgstr "" #: kvpmconfigdialog.cpp:244 kvpmconfigdialog.cpp:318 kvpmconfigdialog.cpp:391 msgid "Remaining space" msgstr "" #: kvpmconfigdialog.cpp:245 msgid "Usage of device" msgstr "" #: kvpmconfigdialog.cpp:246 msgid "Volume group" msgstr "" #: kvpmconfigdialog.cpp:247 msgid "Partition flags" msgstr "" #: kvpmconfigdialog.cpp:248 kvpmconfigdialog.cpp:400 msgid "Mount point" msgstr "" #: kvpmconfigdialog.cpp:249 msgid "Expand device tree" msgstr "" #: kvpmconfigdialog.cpp:261 kvpmconfigdialog.cpp:334 msgid "Show the path to the device, /dev/sda1 for example." msgstr "" #: kvpmconfigdialog.cpp:262 msgid "Show the type of partition, 'extended' for example." msgstr "" #: kvpmconfigdialog.cpp:263 kvpmconfigdialog.cpp:335 kvpmconfigdialog.cpp:416 msgid "Show the storage capacity in megabytes, gigabytes or terabytes." msgstr "" #: kvpmconfigdialog.cpp:264 kvpmconfigdialog.cpp:336 kvpmconfigdialog.cpp:417 msgid "Show the remaining storage in megabytes, gigabytes or terabytes." msgstr "" #: kvpmconfigdialog.cpp:265 msgid "" "Show how the partition is being used. Usually the type of filesystem, such " "as ext4, \n" "swap space or as a physical volume." msgstr "" #: kvpmconfigdialog.cpp:267 msgid "" "If the partition is a physical volume this column shows the volume group it " "is in." msgstr "" #: kvpmconfigdialog.cpp:268 msgid "Show any flags, such a 'boot.'" msgstr "" #: kvpmconfigdialog.cpp:269 kvpmconfigdialog.cpp:428 msgid "Show the mount point if the partition has a mounted filesystem." msgstr "" #: kvpmconfigdialog.cpp:270 msgid "" "This determines if all partitions of all devices get shown at start up. \n" " The user can still expand or collapse the items by clicking on them." msgstr "" #: kvpmconfigdialog.cpp:282 msgid "At start up:" msgstr "" #: kvpmconfigdialog.cpp:293 msgid "Physical Volume Table" msgstr "" #: kvpmconfigdialog.cpp:316 kvpmconfigdialog.cpp:389 pvproperties.cpp:110 msgid "Volume name" msgstr "" #: kvpmconfigdialog.cpp:317 kvpmconfigdialog.cpp:390 pvtree.cpp:48 #: vgtree.cpp:35 msgid "Size" msgstr "" #: kvpmconfigdialog.cpp:319 msgid "Used space" msgstr "" #: kvpmconfigdialog.cpp:320 pvtree.cpp:50 vgtree.cpp:37 msgid "State" msgstr "" #: kvpmconfigdialog.cpp:321 pvtree.cpp:50 msgid "Allocatable" msgstr "" #: kvpmconfigdialog.cpp:322 kvpmconfigdialog.cpp:399 pvtree.cpp:51 #: vgtree.cpp:38 msgid "Tags" msgstr "" #: kvpmconfigdialog.cpp:323 msgid "Logical Volumes" msgstr "" #: kvpmconfigdialog.cpp:337 msgid "Show the used storage in megabytes, gigabytes or terabytes." msgstr "" #: kvpmconfigdialog.cpp:338 msgid "Show the state, either 'active' or 'inactive.'" msgstr "" #: kvpmconfigdialog.cpp:339 msgid "" "Shows whether or not the physical volume can have its extents allocated." msgstr "" #: kvpmconfigdialog.cpp:340 msgid "List any tags associated with the physical volume." msgstr "" #: kvpmconfigdialog.cpp:341 msgid "List any logical volumes associated with the physical volume." msgstr "" #: kvpmconfigdialog.cpp:358 msgid "Logical volume tree" msgstr "" #: kvpmconfigdialog.cpp:392 msgid "Volume type" msgstr "" #: kvpmconfigdialog.cpp:393 msgid "Filesystem type" msgstr "" #: kvpmconfigdialog.cpp:394 msgid "Stripe count" msgstr "" #: kvpmconfigdialog.cpp:395 vgtree.cpp:37 msgid "Stripe size" msgstr "" #: kvpmconfigdialog.cpp:396 msgid "(%)Data/Copy" msgstr "" #: kvpmconfigdialog.cpp:397 msgid "Volume state" msgstr "" #: kvpmconfigdialog.cpp:398 msgid "Volume access" msgstr "" #: kvpmconfigdialog.cpp:415 msgid "Show the volume name." msgstr "" #: kvpmconfigdialog.cpp:418 msgid "Show the type of volume, 'mirror' or 'linear,' for example." msgstr "" #: kvpmconfigdialog.cpp:419 msgid "Show the filesystem type on the volume, 'ext3' or 'swap,' for example." msgstr "" #: kvpmconfigdialog.cpp:420 msgid "Show the number of stripes on the volume, if it is striped" msgstr "" #: kvpmconfigdialog.cpp:421 msgid "Show the size of the stripes, if it is striped" msgstr "" #: kvpmconfigdialog.cpp:422 msgid "" "For a mirror, show the percentage of the mirror synced. \n" "For a snapshot, show the percentage of the snapshot used. \n" "For a pvmove, show the percentage of the move completed." msgstr "" #: kvpmconfigdialog.cpp:425 msgid "Show the state, 'active' or 'invalid,' for example." msgstr "" #: kvpmconfigdialog.cpp:426 msgid "Show access, either read only or read and write." msgstr "" #: kvpmconfigdialog.cpp:427 msgid "List any tags associated with the volume" msgstr "" #: kvpmconfigdialog.cpp:449 msgid "All trees and tables" msgstr "" #: kvpmconfigdialog.cpp:450 msgid "Remaining and used space" msgstr "" #: kvpmconfigdialog.cpp:470 msgid "Show percentage" msgstr "" #: kvpmconfigdialog.cpp:471 msgid "Show total" msgstr "" #: kvpmconfigdialog.cpp:472 msgid "Show both" msgstr "" #: kvpmconfigdialog.cpp:486 msgid "Show warning icon" msgstr "" #: kvpmconfigdialog.cpp:491 msgid "when space falls to or below:" msgstr "" #: kvpmconfigdialog.cpp:498 kvpmconfigdialog.cpp:510 msgid "Never" msgstr "" #: kvpmconfigdialog.cpp:500 msgid "on a filesystem" msgstr "" #: kvpmconfigdialog.cpp:512 msgid "on a physical volume" msgstr "" #: kvpmconfigdialog.cpp:527 msgid "Device Properties Panel" msgstr "" #: kvpmconfigdialog.cpp:539 kvpmconfigdialog.cpp:601 vgtree.cpp:38 msgid "Mount points" msgstr "" #: kvpmconfigdialog.cpp:540 kvpmconfigdialog.cpp:602 msgid "Show the filesystem mount points for the device" msgstr "" #: kvpmconfigdialog.cpp:542 kvpmconfigdialog.cpp:604 msgid "Filesystem uuid" msgstr "" #: kvpmconfigdialog.cpp:543 kvpmconfigdialog.cpp:605 msgid "Show the filesytem UUID" msgstr "" #: kvpmconfigdialog.cpp:545 kvpmconfigdialog.cpp:607 msgid "Filesystem label" msgstr "" #: kvpmconfigdialog.cpp:546 kvpmconfigdialog.cpp:608 msgid "Show the filesystem label" msgstr "" #: kvpmconfigdialog.cpp:560 msgid "Physical Volume Properties Panel" msgstr "" #: kvpmconfigdialog.cpp:570 msgid "Metadata areas" msgstr "" #: kvpmconfigdialog.cpp:571 msgid "Show information about physical volume metadata" msgstr "" #: kvpmconfigdialog.cpp:573 msgid "Physical volume uuid" msgstr "" #: kvpmconfigdialog.cpp:574 msgid "Show the physical volume UUID" msgstr "" #: kvpmconfigdialog.cpp:587 msgid "Logical Volume Properties Panel" msgstr "" #: kvpmconfigdialog.cpp:610 msgid "Logical volume uuid" msgstr "" #: kvpmconfigdialog.cpp:611 msgid "Show the logical volume UUID" msgstr "" #: kvpmconfigdialog.cpp:626 msgid "Volume and Partition Colors" msgstr "" #: kvpmconfigdialog.cpp:630 msgid "" "The graphical display of volumes and partitions can use color to show " "additional information. They can be displayed by type or by the type of " "filesystem on them" msgstr "" #: kvpmconfigdialog.cpp:649 msgid "Use color graphics by volume or partition type" msgstr "" #: kvpmconfigdialog.cpp:650 msgid "Use color graphics by filesystem type" msgstr "" #: kvpmconfigdialog.cpp:964 msgid "Any unused space in a volume group or unpartitioned space on a device" msgstr "" #: kvpmconfigdialog.cpp:973 msgid "Any unpartitioned space inside an extended partition" msgstr "" #: kvpmconfigdialog.cpp:981 msgid "An ordinary partition" msgstr "" #: kvpmconfigdialog.cpp:989 msgid "A partition inside an extended partition" msgstr "" #: lvactions.cpp:54 msgid "Create logical volume..." msgstr "" #: lvactions.cpp:55 msgid "New volume" msgstr "" #: lvactions.cpp:56 lvcreatebase.cpp:106 msgid "Create a new logical volume" msgstr "" #: lvactions.cpp:60 msgid "Create thin pool..." msgstr "" #: lvactions.cpp:61 lvcreatebase.cpp:103 msgid "Create a new thin pool" msgstr "" #: lvactions.cpp:62 msgid "New pool" msgstr "" #: lvactions.cpp:66 msgid "Create thin volume..." msgstr "" #: lvactions.cpp:67 msgid "Create new thin volume" msgstr "" #: lvactions.cpp:68 msgid "Thin volume" msgstr "" #: lvactions.cpp:72 lvactions.cpp:240 msgid "Rename logical volume..." msgstr "" #: lvactions.cpp:73 msgid "Rename a logical volume or thin pool" msgstr "" #: lvactions.cpp:74 vgactions.cpp:82 msgid "Rename" msgstr "" #: lvactions.cpp:78 msgid "Create snapshot..." msgstr "" #: lvactions.cpp:79 msgid "Create a snapshot of a volume" msgstr "" #: lvactions.cpp:80 msgid "Snapshot" msgstr "" #: lvactions.cpp:84 msgid "Create thin snapshot..." msgstr "" #: lvactions.cpp:85 msgid "Create a thin snapshot of a volume" msgstr "" #: lvactions.cpp:86 msgid "Thin snapshot" msgstr "" #: lvactions.cpp:90 msgid "Merge snapshot..." msgstr "" #: lvactions.cpp:91 msgid "Merge a snapshot with its origin" msgstr "" #: lvactions.cpp:92 vgactions.cpp:83 msgid "Merge" msgstr "" #: lvactions.cpp:96 lvactions.cpp:239 msgid "Reduce logical volume..." msgstr "" #: lvactions.cpp:97 msgid "Reduce the size of a logical volume or thin pool" msgstr "" #: lvactions.cpp:98 vgactions.cpp:80 msgid "Reduce" msgstr "" #: lvactions.cpp:102 lvactions.cpp:238 msgid "Extend logical volume..." msgstr "" #: lvactions.cpp:103 msgid "Increase the size of a logical volume or thin pool" msgstr "" #: lvactions.cpp:104 vgactions.cpp:81 msgid "Extend" msgstr "" #: lvactions.cpp:108 msgid "Move physical extents..." msgstr "" #: lvactions.cpp:109 msgid "Move extents to another physical volume" msgstr "" #: lvactions.cpp:110 msgid "Move" msgstr "" #: lvactions.cpp:114 msgid "Change attributes or tags..." msgstr "" #: lvactions.cpp:115 msgid "Change attributes or tags of a volume or thin pool" msgstr "" #: lvactions.cpp:116 pvchange.cpp:50 msgid "Attributes" msgstr "" #: lvactions.cpp:120 lvactions.cpp:241 msgid "Delete logical volume..." msgstr "" #: lvactions.cpp:121 msgid "Delete logical volume or thin pool" msgstr "" #: lvactions.cpp:156 msgid "Add mirror legs to volume..." msgstr "" #: lvactions.cpp:157 msgid "Add mirrors" msgstr "" #: lvactions.cpp:158 msgid "Add mirror legs to volume" msgstr "" #: lvactions.cpp:162 msgid "Change mirror log..." msgstr "" #: lvactions.cpp:163 msgid "Change log" msgstr "" #: lvactions.cpp:164 msgid "Change mirror log" msgstr "" #: lvactions.cpp:168 msgid "Remove mirror legs..." msgstr "" #: lvactions.cpp:169 msgid "Remove mirror legs" msgstr "" #: lvactions.cpp:170 msgid "Remove legs" msgstr "" #: lvactions.cpp:175 msgid "Remove this mirror leg..." msgstr "" #: lvactions.cpp:176 msgid "Remove leg..." msgstr "" #: lvactions.cpp:177 msgid "Remove this mirror leg" msgstr "" #: lvactions.cpp:181 msgid "Repair RAID or mirror..." msgstr "" #: lvactions.cpp:182 msgid "Repair" msgstr "" #: lvactions.cpp:183 msgid "Repair RAID or mirror volume" msgstr "" #: lvactions.cpp:187 msgid "Resync RAID or mirror..." msgstr "" #: lvactions.cpp:188 msgid "Resync" msgstr "" #: lvactions.cpp:189 msgid "Resync RAID or mirror volume" msgstr "" #: lvactions.cpp:272 msgid "Extend thin pool..." msgstr "" #: lvactions.cpp:273 msgid "Reduce thin pool..." msgstr "" #: lvactions.cpp:274 msgid "Rename thin pool..." msgstr "" #: lvactions.cpp:275 msgid "Delete thin pool..." msgstr "" #: lvactions.cpp:444 msgid "Extend thin volume..." msgstr "" #: lvactions.cpp:445 msgid "Reduce thin volume..." msgstr "" #: lvactions.cpp:446 msgid "Rename thin volume..." msgstr "" #: lvactions.cpp:447 msgid "Delete thin volume..." msgstr "" #: lvactionsmenu.cpp:47 msgid "Mirrors and RAID" msgstr "" #: lvchange.cpp:41 msgid "Change Logical Volume Attributes" msgstr "" #: lvchange.cpp:47 msgid "Change volume: %1" msgstr "" #: lvchange.cpp:55 msgctxt "The standard or basic options" msgid "General" msgstr "" #: lvchange.cpp:56 msgctxt "Less used or complex options" msgid "Advanced" msgstr "" #: lvchange.cpp:88 msgid "Make volume available (active)" msgstr "" #: lvchange.cpp:89 msgid "Make volume read only" msgstr "" #: lvchange.cpp:90 msgid "Refresh volume metadata" msgstr "" #: lvchange.cpp:101 msgid "Volume Tags" msgstr "" #: lvchange.cpp:109 pvchange.cpp:82 msgid "Add new tag:" msgstr "" #: lvchange.cpp:117 pvchange.cpp:90 msgid "Remove tag:" msgstr "" #: lvchange.cpp:152 lvcreatebase.cpp:302 msgid "Synchronize with udev" msgstr "" #: lvchange.cpp:156 msgid "Change Volume Polling" msgstr "" #: lvchange.cpp:162 vgchange.cpp:153 msgid "Start polling" msgstr "" #: lvchange.cpp:165 vgchange.cpp:154 msgid "Stop polling" msgstr "" #: lvchange.cpp:168 msgid "Change dmeventd Monitoring" msgstr "" #: lvchange.cpp:174 lvcreatebase.cpp:290 msgid "Monitor with dmeventd" msgstr "" #: lvchange.cpp:175 msgid "Do not monitor" msgstr "" #: lvchange.cpp:176 msgid "Ignore dmeventd" msgstr "" #: lvchange.cpp:195 msgid "Change Kernel Device Numbers" msgstr "" #: lvchange.cpp:201 msgid "Use persistent device numbers" msgstr "" #: lvchange.cpp:207 msgid "Major number: " msgstr "" #: lvchange.cpp:213 msgid "Minor number: " msgstr "" #: lvcreatebase.cpp:83 msgctxt "Less used, dangerous or complex options" msgid "Advanced options" msgstr "" #: lvcreatebase.cpp:92 msgid "Create a thin snapshot of: %1" msgstr "" #: lvcreatebase.cpp:93 msgid "Create A Thin Snapshot" msgstr "" #: lvcreatebase.cpp:95 msgid "Create a snapshot of: %1" msgstr "" #: lvcreatebase.cpp:96 msgid "Create A Snapshot" msgstr "" #: lvcreatebase.cpp:100 msgid "Create a new thin volume" msgstr "" #: lvcreatebase.cpp:101 msgid "Create A New Thin Volume" msgstr "" #: lvcreatebase.cpp:104 msgid "Create A New Thin Pool" msgstr "" #: lvcreatebase.cpp:107 msgid "Create A New Logical Volume" msgstr "" #: lvcreatebase.cpp:112 msgid "Extend thin volume: %1" msgstr "" #: lvcreatebase.cpp:113 msgid "Extend Thin Volume" msgstr "" #: lvcreatebase.cpp:115 msgid "Extend thin pool: %1" msgstr "" #: lvcreatebase.cpp:116 msgid "Extend Thin Pool" msgstr "" #: lvcreatebase.cpp:118 msgid "Extend volume: %1" msgstr "" #: lvcreatebase.cpp:119 msgid "Extend Volume" msgstr "" #: lvcreatebase.cpp:124 msgid "Pool: %1" msgstr "" #: lvcreatebase.cpp:178 msgid "Volume name: " msgstr "" #: lvcreatebase.cpp:180 msgid "Pool name (required): " msgstr "" #: lvcreatebase.cpp:193 msgid "Tag (optional): " msgstr "" #: lvcreatebase.cpp:208 msgid "Set read only" msgstr "" #: lvcreatebase.cpp:209 msgid "Write zeros at volume start" msgstr "" #: lvcreatebase.cpp:227 msgid "Extend filesystem with volume" msgstr "" #: lvcreatebase.cpp:291 msgid "Skip initial synchronization of mirror" msgstr "" #: lvcreatebase.cpp:312 msgid "Device minor number: " msgstr "" #: lvcreatebase.cpp:313 msgid "Device major number: " msgstr "" #: lvcreatebase.cpp:329 msgid "Use persistent device numbering" msgstr "" #: lvcreatebase.cpp:568 msgid "Current size: %1" msgstr "" #: lvcreatebase.cpp:660 msgid "Increasing size: +%1" msgstr "" #: lvcreatebase.cpp:662 msgid "Increasing size: %1" msgstr "" #: lvcreatebase.cpp:665 msgid "Increasing extents: +%1" msgstr "" #: lvcreatebase.cpp:667 msgid "Increasing extents: %1" msgstr "" #: lvcreatebase.cpp:672 lvcreatebase.cpp:678 msgid "Maximum volume size: %1" msgstr "" #: lvcreatebase.cpp:674 lvcreatebase.cpp:681 msgid "Maximum volume extents: %1" msgstr "" #: lvcreatebase.cpp:679 msgid "Maximum filesystem size: %1" msgstr "" #: lvcreatebase.cpp:682 msgid "Maximum filesystem extents: %1" msgstr "" #: lvcreatebase.cpp:702 msgid "(with %1 stripes)" msgstr "" #: lvcreatebase.cpp:704 msgid "(linear volume)" msgstr "" #: lvcreatebase.cpp:707 msgid "(LVM2 mirror with %1 legs and %2 stripes)" msgstr "" #: lvcreatebase.cpp:709 msgid "(LVM2 mirror with %1 legs)" msgstr "" #: lvcreatebase.cpp:711 msgid "(RAID 1 mirror with %1 legs)" msgstr "" #: lvcreatebase.cpp:713 msgid "(RAID 4 with %1 stripes + 1 parity)" msgstr "" #: lvcreatebase.cpp:715 msgid "(RAID 5 with %1 stripes + 1 parity)" msgstr "" #: lvcreatebase.cpp:717 msgid "(RAID 6 with %1 stripes + 2 parity)" msgstr "" #: lvcreate.cpp:225 msgid "Chunk size: " msgstr "" #: lvcreate.cpp:227 vgcreate.cpp:351 vgcreate.cpp:361 vgcreate.cpp:371 #: vgextend.cpp:224 vgextend.cpp:234 vgextend.cpp:244 msgid "default" msgstr "" #: lvcreate.cpp:283 msgid "Extents smaller than 4KiB can not be striped" msgstr "" #: lvcreate.cpp:321 lvcreate.cpp:645 lvcreate.cpp:668 msgid " + 1 parity" msgstr "" #: lvcreate.cpp:325 lvcreate.cpp:690 msgid " + 2 parity" msgstr "" #: lvcreate.cpp:355 msgid "Mirror log: " msgstr "" #: lvcreate.cpp:386 msgid "Number of mirror legs: " msgstr "" #: lvcreate.cpp:404 msgid "Linear" msgstr "" #: lvcreate.cpp:412 lvcreate.cpp:437 msgid "LVM2 Mirror" msgstr "" #: lvcreate.cpp:413 lvcreate.cpp:438 msgid "RAID 1 Mirror" msgstr "" #: lvcreate.cpp:414 lvcreate.cpp:442 msgid "RAID 4" msgstr "" #: lvcreate.cpp:415 lvcreate.cpp:443 msgid "RAID 5" msgstr "" #: lvcreate.cpp:416 lvcreate.cpp:447 msgid "RAID 6" msgstr "" #: lvcreate.cpp:451 msgid "Volume type: " msgstr "" #: lvcreate.cpp:534 msgid "Selected size less than existing size" msgstr "" #: lvcreate.cpp:548 lvcreate.cpp:556 msgid "Selected size exceeds maximum size" msgstr "" #: lvcreate.cpp:1015 thincreate.cpp:189 msgid "Volumes can not be extended while physical volumes are missing" msgstr "" #: lvcreate.cpp:1017 thincreate.cpp:191 msgid "Volumes can not be created while physical volumes are missing" msgstr "" #: lvcreate.cpp:1019 msgid "" "All free physical volume extents in this group are locked against allocation" msgstr "" #: lvcreate.cpp:1022 msgid "There are no free extents in this volume group" msgstr "" #: lvcreate.cpp:1030 thincreate.cpp:198 msgid "" "If this volume has a filesystem or data, it will need to be extended later " "by an appropriate tool. \n" " \n" "Currently, only the ext2, ext3, ext4, xfs, jfs, ntfs and reiserfs file " "systems are supported for extension. " msgstr "" #: lvcreate.cpp:1035 thincreate.cpp:203 msgid "" "This filesystem seems to be as large as it can get, it will not be extended " "with the volume" msgstr "" #: lvcreate.cpp:1037 thincreate.cpp:205 msgid "" "ntfs cannot be extended while mounted. The filesystem will need to be " "extended later or unmounted before the volume is extended." msgstr "" #: lvcreate.cpp:1049 msgid "Insufficient allocatable physical volumes to extend this volume" msgstr "" #: lvcreate.cpp:1056 msgid "Snapshot origins cannot be extended while active" msgstr "" #: lvcreate.cpp:1062 thincreate.cpp:218 msgid "Volumes cannot be extended with open or mounted snapshots" msgstr "" #: lvcreate.cpp:1126 thincreate.cpp:283 msgid "Volume deactivation failed, volume not extended" msgstr "" #: lvcreate.cpp:1132 thincreate.cpp:289 msgid "Volume extension failed" msgstr "" #: lvcreate.cpp:1141 thincreate.cpp:298 msgid "Volume activation failed, filesystem not extended" msgstr "" #: lvcreate.cpp:1143 thincreate.cpp:300 msgid "Volume activation failed" msgstr "" #: lvcreate.cpp:1146 lvcreate.cpp:1155 msgid "Filesystem extention failed" msgstr "" #: lvmconfig.cpp:121 processprogress.cpp:122 msgid "%1 produced this output: %2" msgstr "" #: lvmconfig.cpp:123 processprogress.cpp:124 msgid "%1 crashed with this output: %2" msgstr "" #: lvmconfig.cpp:126 processprogress.cpp:95 msgid "Executable: '%1' not found" msgstr "" #: lvproperties.cpp:108 msgid "not mounted" msgstr "" #: lvproperties.cpp:141 msgid "Logical Volume UUID" msgstr "" #: lvproperties.cpp:218 msgid ", locked" msgstr "" #: lvproperties.cpp:223 lvproperties.cpp:243 msgid "Total Extents: %1" msgstr "" #: lvproperties.cpp:224 lvproperties.cpp:244 msgid "Total Size: %1" msgstr "" #: lvproperties.cpp:227 lvproperties.cpp:288 lvproperties.cpp:309 #: lvproperties.cpp:320 msgid "Access: r/w" msgstr "" #: lvproperties.cpp:229 lvproperties.cpp:290 lvproperties.cpp:311 #: lvproperties.cpp:322 msgid "Access: r/o" msgstr "" #: lvproperties.cpp:231 msgid "Chunk Size: %1" msgstr "" #: lvproperties.cpp:234 lvproperties.cpp:296 msgid "Zero new blocks: Yes" msgstr "" #: lvproperties.cpp:236 lvproperties.cpp:298 msgid "Zero new blocks: No" msgstr "" #: lvproperties.cpp:238 msgid "Discards: %1" msgstr "" #: lvproperties.cpp:239 lvproperties.cpp:300 lvproperties.cpp:313 #: lvproperties.cpp:324 vginfolabels.cpp:121 msgid "Policy: %1" msgstr "" #: lvproperties.cpp:250 lvproperties.cpp:266 lvproperties.cpp:304 #: pvgroupbox.cpp:288 pvmove.cpp:507 msgid "Extents: %1" msgstr "" #: lvproperties.cpp:254 lvproperties.cpp:272 lvproperties.cpp:275 msgid "Stripes: %1 of %2" msgstr "" #: lvproperties.cpp:256 lvproperties.cpp:277 msgid "Stripes: none" msgstr "" #: lvproperties.cpp:270 lvproperties.cpp:279 pvproperties.cpp:186 msgid "Total extents: %1" msgstr "" #: lvproperties.cpp:271 lvproperties.cpp:280 msgid "Total size: %1" msgstr "" #: lvproperties.cpp:285 lvproperties.cpp:317 msgid "Filesystem: %1" msgstr "" #: lvproperties.cpp:329 msgid "Origin: %1" msgstr "" #: lvproperties.cpp:345 lvproperties.cpp:359 msgid "Physical Volumes" msgstr "" #: lvproperties.cpp:347 msgid "Thin Pool" msgstr "" #: lvpropertiesstack.cpp:96 msgid "segment" msgstr "" #: lvpropertiesstack.cpp:101 msgid "Snapshots" msgstr "" #: lvreduce.cpp:38 msgid "Reduce Thin Pool" msgstr "" #: lvreduce.cpp:40 msgid "Reduce Logical Volume" msgstr "" #: lvreduce.cpp:53 msgid "" "If this Inactive logical volume is reduced any data it " "contains will be lost!" msgstr "" #: lvreduce.cpp:56 msgid "" "Only the ext2, ext3 and ext4 file systems are supported for file system " "reduction. If this logical volume is reduced any data it contains " "will be lost!" msgstr "" #: lvreduce.cpp:74 msgid "The filesystem must be unmounted first" msgstr "" #: lvreduce.cpp:77 msgid "Reducing thin pools isn't supported yet" msgstr "" #: lvreduce.cpp:102 msgid "The filesystem is already as small as it can be" msgstr "" #: lvreduce.cpp:128 msgid "Reduce thin pool: %1" msgstr "" #: lvreduce.cpp:130 msgid "Reduce logical volume: %1" msgstr "" #: lvreduce.cpp:133 msgid "Estimated minimum size: %1" msgstr "" #: lvremove.cpp:52 msgid "Delete Thin Pool" msgstr "" #: lvremove.cpp:55 msgid "Delete Volume" msgstr "" #: lvremove.cpp:69 msgid "Delete the thin pool named: %1?" msgstr "" #: lvremove.cpp:71 msgid "Delete the volume named: %1?" msgstr "" #: lvremove.cpp:72 msgid "Any data on it will be lost." msgstr "" #: lvremove.cpp:76 msgid "The thin pool: %1 has dependent volumes." msgstr "" #: lvremove.cpp:78 msgid "The volume: %1 has dependent volumes." msgstr "" #: lvremove.cpp:80 msgid "The following volumes will all be deleted:" msgstr "" #: lvremove.cpp:103 msgid "Are you certain you want to delete these volumes?" msgstr "" #: lvremove.cpp:104 msgid "Any data on them will be lost." msgstr "" #: lvremove.cpp:146 msgid "A volume of this thin pool is busy or mounted. It can not be deleted." msgstr "" #: lvremove.cpp:148 msgid "A snapshot of this origin is busy or mounted. It can not be deleted." msgstr "" #: lvrename.cpp:36 lvrename.cpp:50 msgid "Rename Logical Volume" msgstr "" #: lvrename.cpp:48 msgid "Rename Thin Pool" msgstr "" #: lvrename.cpp:56 msgid "Current name: %1" msgstr "" #: lvrename.cpp:64 msgid "New name: " msgstr "" #: lvsizechartseg.cpp:153 msgid "free space" msgstr "" #: main.cpp:66 msgid "This program must be run as root (uid = 0) " msgstr "" #: main.cpp:67 msgid "Insufficient Privilege" msgstr "" #: maintabwidget.cpp:51 msgid "Group: %1" msgstr "" #: maxfs.cpp:39 msgid "Extend the filesystem on: %1 to fill the entire volume?" msgstr "" #: maxfs.cpp:40 msgid "'ntfs' filesystem must be unmounted before extending." msgstr "" #: maxfs.cpp:41 msgid "" "Extending is only supported for ext2, ext3, ext4, jfs, xfs, ntfs and " "Reiserfs. The correct executables for file system extension must also be " "present." msgstr "" #: maxfs.cpp:69 msgid "" "Filesystem extending is only supported for ext2, ext3, ext4, jfs, xfs, ntfs " "and Reiserfs. Physical volumes can also be extended. The correct executables " "for file system extension must be present" msgstr "" #: maxfs.cpp:72 maxfs.cpp:113 msgid "" "Physical volumes cannot be extended if they contain any active logical " "volumes" msgstr "" #: maxfs.cpp:81 maxfs.cpp:112 msgid "Extend the physical volume on: %1 to fill the entire partition?" msgstr "" #: maxfs.cpp:84 msgid "Extend the filesystem on: %1 to fill the entire partition?" msgstr "" #: mkfs.cpp:66 msgid "" "Writing a new file system on %1 will delete any existing data on it." msgstr "" #: mkfs.cpp:69 msgid "" "The volume: %1 is mounted. It must be unmounted before a new " "filesystem can be written on it" msgstr "" #: mkfs.cpp:97 msgid "Write or remove filesystem on: %1" msgstr "" #: mkfs.cpp:104 mount.cpp:89 msgid "Filesystem Type" msgstr "" #: mkfs.cpp:105 msgid "Standard Ext Options" msgstr "" #: mkfs.cpp:106 msgid "Additional Ext4 Options" msgstr "" #: mkfs.cpp:110 msgid "Write Filesystem" msgstr "" #: mkfs.cpp:127 msgid "Select Filesystem" msgstr "" #: mkfs.cpp:139 msgid "Linux swap" msgstr "" #: mkfs.cpp:164 msgid "Optional name or label: " msgstr "" #: mkfs.cpp:193 msgid "Some filesystems can not use a space this large and have been disabled." msgstr "" #: mkfs.cpp:228 msgid "" "If enabled, these options override the mkfs defaults for ext2, ext3 and ext4 " "filesystems." msgstr "" #: mkfs.cpp:281 msgid "" "If enabled, these options override the mkfs defaults for settings that only " "apply to ext4 filesystems." msgstr "" #: mkfs.cpp:326 msgid "Reserved space: " msgstr "" #: mkfs.cpp:337 msgid "Block size: " msgstr "" #: mkfs.cpp:341 mkfs.cpp:355 mkfs.cpp:368 mkfs.cpp:379 msgctxt "Let the program decide" msgid "default" msgstr "" #: mkfs.cpp:351 msgid "Inode size: " msgstr "" #: mkfs.cpp:365 msgid "Bytes / inode: " msgstr "" #: mkfs.cpp:376 msgid "Total inodes: " msgstr "" #: mkfs.cpp:399 msgid "Use extents" msgstr "" #: mkfs.cpp:400 msgid "Flexible block group layout" msgstr "" #: mkfs.cpp:401 msgid "Enable files over 2TB" msgstr "" #: mkfs.cpp:402 msgid "Don't init all block groups" msgstr "" #: mkfs.cpp:403 msgid "Don't init all inodes" msgstr "" #: mkfs.cpp:404 msgid "Unlimited subdirectories" msgstr "" #: mkfs.cpp:405 msgid "Nanosecond timestamps" msgstr "" #: mkfs.cpp:421 msgid "Striping" msgstr "" #: mkfs.cpp:430 msgid "Stride size in blocks: " msgstr "" #: mkfs.cpp:442 msgid "Strides per stripe: " msgstr "" #: mkfs.cpp:463 msgid "Extended attributes" msgstr "" #: mkfs.cpp:464 msgid "Resize inode" msgstr "" #: mkfs.cpp:466 msgid "Directory B-Tree index" msgstr "" #: mkfs.cpp:467 msgid "Store filetype in inode" msgstr "" #: mkfs.cpp:468 msgid "Sparse superblock" msgstr "" #: mount.cpp:76 msgid "Device to mount: %1" msgstr "" #: mount.cpp:82 msgid "Main" msgstr "" #: mount.cpp:83 msgid "Options" msgstr "" #: mount.cpp:117 msgid "Specify another fileystem:" msgstr "" #: mount.cpp:188 msgid "Common Options" msgstr "" #: mount.cpp:217 msgid "Always use synchronous I/O" msgstr "" #: mount.cpp:218 msgid "Allow writing in addition to reading" msgstr "" #: mount.cpp:219 msgid "Allow the suid bit to have effect" msgstr "" #: mount.cpp:220 msgid "Allow the use of block and special devices" msgstr "" #: mount.cpp:221 msgid "Allow the execution of binary files" msgstr "" #: mount.cpp:222 msgid "Allow manditory file locks" msgstr "" #: mount.cpp:223 msgid "Allow use of access control lists" msgstr "" #: mount.cpp:240 msgid "Update atime" msgstr "" #: mount.cpp:248 msgid "Journaling" msgstr "" #: mount.cpp:271 msgid "Always update atime" msgstr "" #: mount.cpp:272 msgid "Do not update atime" msgstr "" #: mount.cpp:273 msgid "" "Access time is only updated if the previous access time was earlier than the " "current modify or change time" msgstr "" #: mount.cpp:276 msgid "Do not update atime for directory access" msgstr "" #: mount.cpp:280 msgid "Filesystem specific mount options" msgstr "" #: mount.cpp:282 msgid "comma separated list of additional mount options" msgstr "" #: mount.cpp:351 msgid "Mount Point" msgstr "" #: mount.cpp:362 msgid "Browse" msgstr "" #: partadd.cpp:75 msgid "Select type: " msgstr "" #: partadd.cpp:100 msgid "Primary" msgstr "" #: partadd.cpp:101 msgid "Extended" msgstr "" #: partadd.cpp:104 msgid "Logical" msgstr "" #: partadd.cpp:147 msgid "Partitions less than two MiB are not supported" msgstr "" #: partbase.cpp:57 msgid "This disk already has %1 primary partitions, the maximum" msgstr "" #: partbase.cpp:60 msgid "" "This should not happen. Try selecting the freespace and not the partiton " "itself" msgstr "" #: partbase.cpp:67 msgid "Mounted partitions cannot be changed" msgstr "" #: partbase.cpp:87 msgid "No free space found" msgstr "" #: partbase.cpp:92 msgid "Not enough free space for a new partition" msgstr "" #: partbase.cpp:109 msgid "" "Not enough free space to move or extend this partition and it can not be " "shrunk" msgstr "" #: partbase.cpp:129 msgid "Move or resize a partition" msgstr "" #: partbase.cpp:138 msgid "Create A New Partition" msgstr "" #: partbase.cpp:140 msgid "Resize Or Move A Partition" msgstr "" #: partbase.cpp:146 msgid "Device: %1" msgstr "" #: partchange.cpp:65 msgid "" "Changes to the partition table can cause unintentional and permanent data " "loss. If the partition holds important data, you really should back it up " "before continuing." msgstr "" #: partchange.cpp:84 msgid "" "Shrinking this filesystem is not supported, only growing or moving it is " "possible.\n" "\n" "Note: currently only ext2, ext3, ext4 and physical volumes can be shrunk." msgstr "" #: partchange.cpp:89 msgid "" "If this partition is enlarged, any filesystem or data on it will need to be " "extended separately. Shrinking this partition is not supported.\n" "\n" "Note: currently only ext2, ext3, ext4 and physical volumes can be both " "shrunk and grown, while Reiserfs, ntfs, jfs and xfs can be grown only." msgstr "" #: partchange.cpp:119 msgid "" "This partition is on the same device with partitions that are busy or " "mounted. If at all possible they should be unmounted before proceeding. " "Otherise changes to the partition table may not be recognized by the kernel." msgstr "" #: partchange.cpp:239 msgid "Moving data" msgstr "" #: partchange.cpp:250 partchange.cpp:265 partchange.cpp:276 partchange.cpp:291 msgid "Move failed: could not read from device" msgstr "" #: partchange.cpp:255 partchange.cpp:269 partchange.cpp:280 partchange.cpp:296 msgid "Move failed: could not write to device" msgstr "" #: partchange.cpp:311 msgid "Syncing device" msgstr "" #: partchange.cpp:362 msgid "Filesystem shrink failed" msgstr "" #: partchange.cpp:407 msgid "Partition shrink failed" msgstr "" #: partchange.cpp:471 partchange.cpp:489 msgid "Partition extension failed" msgstr "" #: partchange.cpp:575 msgid "Repartitioning failed: data not moved" msgstr "" #: partflag.cpp:42 partflag.cpp:48 msgid "Set Partition Flags" msgstr "" #: partitiongraphic.cpp:158 msgid "Reduce size: -%1" msgstr "" #: partitiongraphic.cpp:160 msgid "Size: no change" msgstr "" #: partitiongraphic.cpp:162 msgid "Extend size: %1" msgstr "" #: partitiongraphic.cpp:165 msgid "Move (left): -%1" msgstr "" #: partitiongraphic.cpp:167 msgid "Move: no change" msgstr "" #: partitiongraphic.cpp:169 msgid "Move (right): %1" msgstr "" #: partitiongraphic.cpp:171 msgid "Preceding free space: %1" msgstr "" #: partitiongraphic.cpp:172 msgid "Following free space: %1" msgstr "" #: partremove.cpp:31 msgid "Delete partition: %1? Any data on that partition will be lost." msgstr "" #: partremove.cpp:47 msgid "Delete partition: %1?" msgstr "" #: processprogress.cpp:61 msgid "Running program: %1" msgstr "" #: processprogress.cpp:62 msgid "Cancel" msgstr "" #: processprogress.cpp:147 msgid "Really kill process %1" msgstr "" #: processprogress.cpp:160 msgid "Waiting for process to finish" msgstr "" #: pvactions.cpp:39 msgid "Move all physical extents" msgstr "" #: pvactions.cpp:40 msgid "Move all" msgstr "" #: pvactions.cpp:41 msgid "Move all extents to another physical volume" msgstr "" #: pvactions.cpp:46 vgsplit.cpp:177 msgid "Remove" msgstr "" #: pvactions.cpp:47 msgid "Remove physical volume from volume group" msgstr "" #: pvactions.cpp:51 pvactions.cpp:53 msgid "Change physical volume attributes" msgstr "" #: pvactions.cpp:52 msgid "attributes" msgstr "" #: pvchange.cpp:37 msgid "Change Physical Volume Attributes" msgstr "" #: pvchange.cpp:45 msgid "Changing volume: %1" msgstr "" #: pvchange.cpp:54 msgid "Allow allocation of extents" msgstr "" #: pvchange.cpp:59 msgid "Use metadata areas on this volume" msgstr "" #: pvchange.cpp:66 msgid "Generate new UUID for this volume" msgstr "" #: pvchange.cpp:72 msgid "Change tags" msgstr "" #: pvgroupbox.cpp:53 msgid "Target Physical Volumes" msgstr "" #: pvgroupbox.cpp:55 pvgroupbox.cpp:109 msgid "Available Physical Volumes" msgstr "" #: pvgroupbox.cpp:69 msgid "No suitable volumes found" msgstr "" #: pvgroupbox.cpp:111 msgid "Physical Volume" msgstr "" #: pvgroupbox.cpp:128 msgid "none found" msgstr "" #: pvgroupbox.cpp:284 msgid "Contiguous space: %1" msgstr "" #: pvgroupbox.cpp:285 msgid "Contiguous extents: %1" msgstr "" #: pvgroupbox.cpp:287 msgid "Space: %1" msgstr "" #: pvgroupbox.cpp:338 msgid "Select all" msgstr "" #: pvgroupbox.cpp:339 msgid "Clear all" msgstr "" #: pvmove.cpp:54 msgid "Do you wish to restart all interrupted physical volume moves?" msgstr "" #: pvmove.cpp:67 msgid "Do you wish to abort all physical volume moves currently in progress?" msgstr "" #: pvmove.cpp:102 pvmove.cpp:121 msgid "None of the extents on this volume can be moved" msgstr "" #: pvmove.cpp:163 msgid "There are no allocatable physical volumes with space to move to" msgstr "" #: pvmove.cpp:184 msgid "Move Physical Extents" msgstr "" #: pvmove.cpp:192 msgid "Source Physical Volumes" msgstr "" #: pvmove.cpp:253 msgid "Source Physical Volume" msgstr "" #: pvmove.cpp:397 msgid "Move only physical extents on:" msgstr "" #: pvmove.cpp:402 msgid "Move physical extents" msgstr "" #: pvmove.cpp:433 msgid "Logical Volumes To Move" msgstr "" #: pvmove.cpp:451 msgid "" msgstr "" #: pvmove.cpp:459 msgid "Movable space: %1" msgstr "" #: pvmove.cpp:460 msgid "Movable extents: %1" msgstr "" #: pvproperties.cpp:113 msgid "Start" msgstr "" #: pvproperties.cpp:116 msgid "End" msgstr "" #: pvproperties.cpp:119 sizeselectorbox.cpp:78 msgid "Extents" msgstr "" #: pvproperties.cpp:139 pvproperties.cpp:145 pvproperties.cpp:151 #: pvproperties.cpp:163 pvproperties.cpp:169 pvproperties.cpp:175 msgid "%1" msgstr "" #: pvtree.cpp:48 msgctxt "The name of the device" msgid "Name" msgstr "" #: pvtree.cpp:49 msgctxt "Unused space" msgid "Remaining" msgstr "" #: pvtree.cpp:49 msgctxt "Space used up" msgid "Used" msgstr "" #: pvtree.cpp:52 vgchange.cpp:189 msgid "Logical volumes" msgstr "" #: pvtree.cpp:61 msgid "Physical volume device" msgstr "" #: pvtree.cpp:62 msgid "Total size of physical volume" msgstr "" #: pvtree.cpp:63 msgid "Free space on physical volume" msgstr "" #: pvtree.cpp:64 msgid "Space used on physical volume" msgstr "" #: pvtree.cpp:65 msgid "A physcial volume is active if it has logical volumes that are active" msgstr "" #: pvtree.cpp:66 msgid "If physical volume allows more extents to be allocated" msgstr "" #: pvtree.cpp:67 vgtree.cpp:55 msgid "Optional tags for physical volume" msgstr "" #: pvtree.cpp:68 msgid "Logical volumes on physical volume" msgstr "" #: pvtree.cpp:100 msgid "MISSING" msgstr "" #: pvtree.cpp:102 msgid "MISSING %1" msgstr "" #: pvtree.cpp:137 msgid "This physical volume can not be found" msgstr "" #: pvtree.cpp:155 msgid "Active" msgstr "" #: pvtree.cpp:158 msgid "Inactive" msgstr "" #: removemirror.cpp:39 msgid "Remove Mirrors" msgstr "" #: removemirror.cpp:46 msgid "Select mirror legs to remove from %1" msgstr "" #: removemirrorleg.cpp:30 msgid "Remove mirror leg: %1 ?" msgstr "" #: repairmissing.cpp:49 msgid "This volume has no missing physical volumes" msgstr "" #: repairmissing.cpp:52 msgid "No suitable physical volumes found" msgstr "" #: repairmissing.cpp:61 msgid "Repair mirror: %1" msgstr "" #: repairmissing.cpp:62 msgid "Repair Mirror" msgstr "" #: repairmissing.cpp:64 msgid "Repair RAID device: %1" msgstr "" #: repairmissing.cpp:65 msgid "Repair RAID device" msgstr "" #: repairmissing.cpp:72 msgid "Replace missing physical volumes" msgstr "" #: repairmissing.cpp:73 vgremovemissing.cpp:36 msgid "Remove missing physical volumes" msgstr "" #: repairmissing.cpp:89 msgid "No suitable replacement physical volumes found." msgstr "" #: repairmissing.cpp:90 msgid "Remove missing physical volumes from:" msgstr "" #: resync.cpp:30 msgid "" "Volume %1 is in use or mounted and cannot be deactivated for re-" "synchronization" msgstr "" #: resync.cpp:38 msgid "Really re-synchronize %1? That could take a long time." msgstr "" #: sizeselectorbox.cpp:98 msgid "Volume Size" msgstr "" #: sizeselectorbox.cpp:100 sizeselectorbox.cpp:120 msgid "Lock selected size" msgstr "" #: sizeselectorbox.cpp:111 msgid "Partition Start" msgstr "" #: sizeselectorbox.cpp:112 msgid "Lock partition start" msgstr "" #: sizeselectorbox.cpp:119 msgid "Partition Size" msgstr "" #: sizeselectorbox.cpp:127 msgid "Prevent shrinking" msgstr "" #: sizeselectorbox.cpp:143 msgid "New start:" msgstr "" #: sizeselectorbox.cpp:145 msgid "New size:" msgstr "" #: snapmerge.cpp:30 msgid "Merge snapshot: %1 with origin: %2?" msgstr "" #: tablecreate.cpp:34 msgid "" "Writing a new partition table to this device, or removing the old one, will " "cause any existing data on it to be permanently lost" msgstr "" #: tablecreate.cpp:62 msgid "Create Partition Table" msgstr "" #: tablecreate.cpp:69 msgid "Create partition table on:" msgstr "" #: tablecreate.cpp:74 msgid "Table Types" msgstr "" #: tablecreate.cpp:79 msgid "MS-DOS" msgstr "" #: tablecreate.cpp:80 msgid "GPT" msgstr "" #: tablecreate.cpp:81 msgid "Remove table" msgstr "" #: thincreate.cpp:210 msgid "Snapshot origins cannot be extended while open or mounted" msgstr "" #: topwindow.cpp:65 msgid "Storage Devices" msgstr "" #: topwindow.cpp:270 msgid "Toolbar icon size" msgstr "" #: topwindow.cpp:272 msgid "Small icons (16x16)" msgstr "" #: topwindow.cpp:273 msgid "Medium icons (22x22)" msgstr "" #: topwindow.cpp:274 msgid "Large icons (32x32)" msgstr "" #: topwindow.cpp:275 msgid "Huge icons (48x48)" msgstr "" #: topwindow.cpp:317 msgid "Toolbar text placement" msgstr "" #: topwindow.cpp:319 msgid "Icons only" msgstr "" #: topwindow.cpp:320 msgid "Text only" msgstr "" #: topwindow.cpp:321 msgid "Text beside icons" msgstr "" #: topwindow.cpp:322 msgid "Text under icons" msgstr "" #: topwindow.cpp:364 msgid "Settings" msgstr "" #: topwindow.cpp:365 msgid "Show Volume Group Information" msgstr "" #: topwindow.cpp:366 msgid "Show Volume Group Bar Chart" msgstr "" #: topwindow.cpp:367 msgid "Show Device Bar Chart" msgstr "" #: topwindow.cpp:368 msgid "Use Metric SI Units" msgstr "" #: topwindow.cpp:369 msgid "Show Toolbars" msgstr "" #: topwindow.cpp:370 msgid "Configure kvpm..." msgstr "" #: topwindow.cpp:423 msgid "Tools" msgstr "" #: topwindow.cpp:424 msgid "Rescan System" msgstr "" #: topwindow.cpp:425 msgid "Restart interrupted pvmove" msgstr "" #: topwindow.cpp:426 msgid "Abort pvmove" msgstr "" #: topwindow.cpp:479 msgid "File" msgstr "" #: topwindow.cpp:495 msgid "Volume Groups" msgstr "" #: unmount.cpp:60 msgid "Can not unmount: %1, it does not seem to be mounted" msgstr "" #: unmount.cpp:67 unmount.cpp:82 msgid "" "Can not unmount: %1, another volume or device is mounted over the " "same mount point and must be unmounted first" msgstr "" #: unmount.cpp:89 msgid "Unmount Filesystem" msgstr "" #: unmount.cpp:100 msgid "Unmount Filesystem" msgstr "" #: unmount.cpp:108 msgid "%1 is mounted on: %2" msgstr "" #: unmount.cpp:109 msgid "Do you wish to unmount it?" msgstr "" #: unmount.cpp:114 msgid "%1 is mounted at multiple locatations." msgstr "" #: unmount.cpp:116 msgid "Select the ones to unmount:" msgstr "" #: unmount.cpp:133 msgid "" "Some selections have been disabled. Another device or volume is mounted over " "the same mount point and must be unmounted first" msgstr "" #: unmount.cpp:169 unmount.cpp:182 msgid "Unmounting %1 failed with error number: %2 %3" msgstr "" #: vgactions.cpp:55 msgid "Delete Volume Group..." msgstr "" #: vgactions.cpp:56 msgid "Reduce Volume Group..." msgstr "" #: vgactions.cpp:57 msgid "Extend Volume Group..." msgstr "" #: vgactions.cpp:58 msgid "Rename Volume Group..." msgstr "" #: vgactions.cpp:59 msgid "Merge Volume Group..." msgstr "" #: vgactions.cpp:60 msgid "Split Volume Group..." msgstr "" #: vgactions.cpp:61 msgid "Change Volume Group Attributes..." msgstr "" #: vgactions.cpp:62 msgid "Create Volume Group..." msgstr "" #: vgactions.cpp:63 msgid "Import Volume Group..." msgstr "" #: vgactions.cpp:64 msgid "Export Volume Group..." msgstr "" #: vgactions.cpp:65 msgid "Remove Missing Physcial Volumes..." msgstr "" #: vgactions.cpp:67 msgid "Delete Volume Group" msgstr "" #: vgactions.cpp:68 vgreduce.cpp:79 msgid "Reduce Volume Group" msgstr "" #: vgactions.cpp:69 vgextend.cpp:162 msgid "Extend Volume Group" msgstr "" #: vgactions.cpp:70 vgrename.cpp:39 vgrename.cpp:46 msgid "Rename Volume Group" msgstr "" #: vgactions.cpp:71 vgmerge.cpp:38 msgid "Merge Volume Group" msgstr "" #: vgactions.cpp:72 vgsplit.cpp:44 msgid "Split Volume Group" msgstr "" #: vgactions.cpp:73 vgchange.cpp:42 msgid "Change Volume Group Attributes" msgstr "" #: vgactions.cpp:74 vgcreate.cpp:228 msgid "Create Volume Group" msgstr "" #: vgactions.cpp:75 msgid "Import Volume Group" msgstr "" #: vgactions.cpp:76 msgid "Export Volume Group" msgstr "" #: vgactions.cpp:77 msgid "Remove Missing Physcial Volumes" msgstr "" #: vgactions.cpp:84 msgid "Split" msgstr "" #: vgactions.cpp:85 msgid "Change Attributes" msgstr "" #: vgactions.cpp:86 msgid "Create" msgstr "" #: vgactions.cpp:87 msgid "Import" msgstr "" #: vgactions.cpp:88 msgid "Export" msgstr "" #: vgactions.cpp:89 msgid "Remove Missing" msgstr "" #: vgchange.cpp:49 msgid "Change volume group: %1" msgstr "" #: vgchange.cpp:58 msgid "Allow physical volume addition and removal" msgstr "" #: vgchange.cpp:61 msgid "Refresh metadata" msgstr "" #: vgchange.cpp:64 msgid "Generate new UUID fo group" msgstr "" #: vgchange.cpp:72 msgid "Enable cluster support" msgstr "" #: vgchange.cpp:76 msgid "" "Caution: do not enable cluster support unless you have a working cluster " "running" msgstr "" #: vgchange.cpp:127 msgid "Extent size:" msgstr "" #: vgchange.cpp:141 msgid "Make all logical volumes available (active)" msgstr "" #: vgchange.cpp:142 msgid "Make all logical volumes unavailable (inactive)" msgstr "" #: vgchange.cpp:185 msgid "Change maximum limit for number of volumes" msgstr "" #: vgchange.cpp:190 msgid "Physical volumes" msgstr "" #: vgchange.cpp:205 vgchange.cpp:206 msgid "Currently: %1 Minimum: %2" msgstr "" #: vgchange.cpp:223 vgchange.cpp:250 msgid "unlimited" msgstr "" #: vgcreate.cpp:55 vgextend.cpp:54 msgid "No unused potential physical volumes found" msgstr "" #: vgcreate.cpp:73 vgextend.cpp:73 msgid "" "If a device or partition is added to a volume group, any data currently on " "that device or partition will be lost." msgstr "" #: vgcreate.cpp:144 msgid "Creating Group" msgstr "" #: vgcreate.cpp:254 msgid "Group name: " msgstr "" #: vgcreate.cpp:265 msgid "Physical extent size: " msgstr "" #: vgcreate.cpp:299 msgid "Cluster Aware" msgstr "" #: vgcreate.cpp:302 msgid "Automatic Backup" msgstr "" #: vgcreate.cpp:335 vgextend.cpp:208 msgid "1" msgstr "" #: vgcreate.cpp:336 vgextend.cpp:209 msgid "2" msgstr "" #: vgcreate.cpp:341 vgextend.cpp:214 msgid "All values are in KiloBytes" msgstr "" #: vgcreate.cpp:345 vgextend.cpp:218 msgid "Metadata size:" msgstr "" #: vgcreate.cpp:355 vgextend.cpp:228 msgid "Metadata align:" msgstr "" #: vgcreate.cpp:365 vgextend.cpp:238 msgid "Metadata offset:" msgstr "" #: vgexport.cpp:31 msgid "Export volume group: %1?" msgstr "" #: vgextend.cpp:169 msgid "Extend volume group: %1" msgstr "" #: vgextend.cpp:207 msgid "0" msgstr "" #: vgimport.cpp:30 msgid "Import volume group: %1?" msgstr "" #: vginfolabels.cpp:117 msgctxt "Space used up" msgid "Used: %1" msgstr "" #: vginfolabels.cpp:118 msgctxt "Space not used" msgid "Free: %1" msgstr "" #: vginfolabels.cpp:119 msgctxt "Total space on device" msgid "Total: %1" msgstr "" #: vginfolabels.cpp:120 msgid "Format: %1" msgstr "" #: vginfolabels.cpp:122 msgid "Resizable: %1" msgstr "" #: vginfolabels.cpp:123 msgid "Clustered: %1" msgstr "" #: vginfolabels.cpp:124 msgid "Allocatable: %1" msgstr "" #: vginfolabels.cpp:125 msgid "Extent size: %1" msgstr "" #: vginfolabels.cpp:126 msgid "MDA: %1 Used: %2" msgstr "" #: vginfolabels.cpp:127 msgid "UUID: %1" msgstr "" #: vginfolabels.cpp:150 msgid "Max pvs: %1" msgstr "" #: vginfolabels.cpp:152 msgid "Max pvs: Unlimited" msgstr "" #: vginfolabels.cpp:155 msgid "Max lvs: %1" msgstr "" #: vginfolabels.cpp:157 msgid "Max lvs: Unlimited" msgstr "" #: vgmerge.cpp:44 msgid "Merge volume group: %1" msgstr "" #: vgmerge.cpp:53 msgid "Merge with:" msgstr "" #: vgmerge.cpp:77 msgid "Error: Extent size must match" msgstr "" #: vgmerge.cpp:112 msgid "There is no other volume group to merge with" msgstr "" #: vgmerge.cpp:119 msgid "The volume group to merge must not have active logical volumes" msgstr "" #: vgreduce.cpp:43 msgid "Remove Physical Volume" msgstr "" #: vgreduce.cpp:49 msgid "Remove physical volume: %1?" msgstr "" #: vgreduce.cpp:58 msgid "This physical volume is still in use and cannot be removed" msgstr "" #: vgreduce.cpp:61 msgid "" "There is only one physical volume in this group, try deleting the group " "instead" msgstr "" #: vgreduce.cpp:64 msgid "" "Physical volume '%1' contains the only usable metadata area for this volume " "group and cannot be removed." msgstr "" #: vgreduce.cpp:90 msgid "There are no physical volumes that can be removed" msgstr "" #: vgreduce.cpp:93 msgid "" "There is only one physical volume in this group, try deleting the group " "instead." msgstr "" #: vgreduce.cpp:96 msgid "" "The only physical volume with no logical volumes on it is '%1.' It contains " "the only usable metadata area for this volume group and cannot be removed." msgstr "" #: vgreduce.cpp:103 msgid "Reduce volume group: %1" msgstr "" #: vgreduce.cpp:109 msgid "This is the only physical volume that can be removed" msgstr "" #: vgreduce.cpp:111 msgid "Select physical volumes to remove them from the volume group" msgstr "" #: vgreduce.cpp:113 msgid "" "Select physical volumes, excluding one, to remove them from the volume group" msgstr "" #: vgreduce.cpp:125 msgid "Unused physical volumes" msgstr "" #: vgreduce.cpp:230 msgid "Cannot remove all the physical volumes with usable metadata areas" msgstr "" #: vgreduce.cpp:242 msgid "A volume group must always have at least one physical volume in it" msgstr "" #: vgremove.cpp:37 msgid "Are you certain you want to delete volume group: %1?" msgstr "" #: vgremovemissing.cpp:49 msgid "" "Removing missing physical volumes may result in data loss. Use with " "extreme care." msgstr "" #: vgrename.cpp:50 msgid "Current volume group name: %1" msgstr "" #: vgrename.cpp:53 msgid "New volume group name: " msgstr "" #: vgsplit.cpp:50 msgid "Volume group to split: %1" msgstr "" #: vgsplit.cpp:59 msgid "New Volume Group Name" msgstr "" #: vgsplit.cpp:78 msgid "Logical volume view" msgstr "" #: vgsplit.cpp:79 msgid "Physical volume view" msgstr "" #: vgsplit.cpp:88 msgid "A volume group must have at least two physical volumes to split group" msgstr "" #: vgsplit.cpp:199 vgsplit.cpp:265 msgid "Original volume group" msgstr "" #: vgsplit.cpp:209 vgsplit.cpp:277 msgid "New volume group" msgstr "" #: vgtree.cpp:35 msgid "Volume" msgstr "" #: vgtree.cpp:35 msgid "type" msgstr "" #: vgtree.cpp:36 msgid "Remaining" msgstr "" #: vgtree.cpp:36 msgid "Filesystem" msgstr "" #: vgtree.cpp:36 msgid "Stripes" msgstr "" #: vgtree.cpp:37 msgid "Data/Copy" msgstr "" #: vgtree.cpp:38 msgid "Access" msgstr "" #: vgtree.cpp:45 msgid "Logical volume name" msgstr "" #: vgtree.cpp:46 msgid "Type of logical volume" msgstr "" #: vgtree.cpp:47 msgid "Total size of the logical volume" msgstr "" #: vgtree.cpp:48 msgid "Free space on logical volume" msgstr "" #: vgtree.cpp:49 msgid "Filesystem type on logical volume, if any" msgstr "" #: vgtree.cpp:50 msgid "Number of stripes if the volume is striped" msgstr "" #: vgtree.cpp:51 msgid "Size of stripes if the volume is striped" msgstr "" #: vgtree.cpp:52 msgid "Percentage of pvmove completed, of mirror synced or of snapshot used up" msgstr "" #: vgtree.cpp:53 msgid "Logical volume state" msgstr "" #: vgtree.cpp:54 msgid "Read and write or Read Only" msgstr "" #: vgtree.cpp:56 msgid "Filesystem mount points, if mounted" msgstr "" #: vgtree.cpp:189 vgtree.cpp:502 msgid "one or more physical volumes are missing" msgstr "" #: vgtree.cpp:193 msgid "origin" msgstr "" #: vgtree.cpp:228 msgid "This filesystem is running out of space" msgstr "" #: vgwarning.cpp:68 msgid "Exported Volume Group" msgstr "" #: vgwarning.cpp:88 msgid "Warning: clustered volume group could not be opened" msgstr "" #: vgwarning.cpp:90 msgid "Warning: volume group could not be opened" msgstr "" #: vgwarning.cpp:109 msgid "Warning: Partial volume group, some physical volumes are missing" msgstr "" #: rc.cpp:1 msgctxt "NAME OF TRANSLATORS" msgid "Your names" msgstr "" #: rc.cpp:2 msgctxt "EMAIL OF TRANSLATORS" msgid "Your emails" msgstr "" kvpm-0.9.10/CMakeLists.txt0000644000175000017500000000236712770324126015556 0ustar benscottbenscottcmake_minimum_required(VERSION 3.1) cmake_policy(SET CMP0059 NEW) project (kvpm) SET(CMAKE_INSTALL_PREFIX "/usr") set(QT_MIN_VERSION "5.6.1") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Gui Widgets ) find_package(ECM 5.23 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_MODULE_PATH}) include(ECMInstallIcons) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(FeatureSummary) # Load the frameworks we need find_package(KF5 5.23 REQUIRED CoreAddons DocTools I18n KDELibs4Support ) # find_package(KDE4 REQUIRED) include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${BLKID_INCLUDE_DIRS}) add_subdirectory( kvpm ) add_subdirectory( docbook ) add_subdirectory( images ) add_subdirectory( icons/local ) add_subdirectory( icons/app ) IF(CMAKE_BUILD_TYPE EQUAL "Debian") SET(CMAKE_BUILD_TYPE "RelWithDebInfo") ENDIF() IF(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE "RelWithDebInfo") ENDIF(NOT CMAKE_BUILD_TYPE) install(FILES kvpm.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) install(FILES "kvpm.1" DESTINATION ${MAN_INSTALL_DIR}/man1 COMPONENT doc) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) kvpm-0.9.10/CHANGELOG0000644000175000017500000002036012770324126014221 0ustar benscottbenscottversion 0.9.10 Better support for adding physical volumes with non standard metadata size or alignment. Started porting to KDE Frameworks 5. Thank you: . version 0.9.9 Mostly small bug fixes and work arounds for problems introduced by recent versions of the lvm2app library. version 0.9.8 Added a dialog for resyncing mirrors and RAID volume in the menu and toolbar. Some limited support for dmraid "fake RAID" devices and for md "multiple device" RAID under volume groups. version 0.9.7 Put toolbars on the device tab for all the menu options available in the device context menu, except for extend volume. version 0.9.6 Put toolbars on the volume group tabs for all the menu options available in the logical and physical volume context menus. version 0.9.5 Added a dialog for setting the flags on a partitions. Ie: boot, swap and bios_grub. version 0.9.4 Put in a new dialog for repairing RAID and mirror volumes that are missing physical volumes. Combined the remove filesystem dialog and the mkfs dialog into one. Improved handling of partial volume groups. version 0.9.3 Added support for displaying thin pools and volumes. Put in a new dialog for creating thin volumes and snaps. The lvremove dialog now warns when deleting thin pools with volumes. version 0.9.2 Added support for raid1, raid4, raid5 and raid6. Added support for the 'contiguous' policy in all dialogs that have a policy option. Normal has always been supported. version 0.9.1 Improved pvmove dialog with regard to mirrors and snapshots by making the not movable parts clearly labeled. version 0.9.0 Made compiling faster by removing #include everywhere. Added more calls to QLabel->setBuddy() to improve mouse-less usage. Reformatted code with program "astyle" to make it more consistent. Improved consistency of dialog appearance too. version 0.8.6 Fixed immediate crash when group is clustered. Fixed configuration dialog buttons to work more as a user would expect. /etc/mtab permissions don't get mangled now. version 0.8.5 It is now possible to cancel a running mkfs process and some other long running process should follow soon. Sizes may now be specified in traditional powers of two (1KiB = 1024) or in powers of ten (1KB = 1000) SI units. version 0.8.4 Added to, and improved, the configuration dialog. Property panels can now be configured. Changed the detection of mounted filesystems to use /proc/self/mountinfo rather than the depracated /etc/mtab file. Fixed a bug that deleted symlinked /etc/mtab and replaced it with a real file. version 0.8.3 Added new configuration options and made showing some panels optional. Improved the appearance of the properties panels and made them look more consistant with the overall look. version 0.8.2 Added a splash screen and moved the progressbar to the mainwindow menubar. Improved the dialog for adding a mirror leg or changing a mirror log. version 0.8.1 Improved and added to the kvpm config options. version 0.8.0 Added icons for most menus. Added several more for the Tree lists of physical volumes, logical volumes and devices to show mount state, active status and missing volumes in partial groups. Improved cmake commands for "make install." version 0.7.8 Added a menu option for running 'fsck -fp' on a filesystem. Removed call to ped_file_system_clobber() which is no longer supported by libparted. version 0.7.7 Changed lvcreate to allow the extention of snapshot origings by shutting them down then extending the lv, reactivating and extending the filesystem. version 0.7.6 Added more selections to the mkfs dialog such a setting inode size and many other advanced options. The lv size chart graphic now is presented in the same order as the volumes appear in the volume group tree even when the tree sort order is changed. Added a new column in the pvtree showing the use/ignoring of metadata areas in a pv. pvchange can set the use or ignoring of metadata and generate a new pv uuid. There is now a dialog for merging snapshots. version 0.7.5 Re-worked and debugged add partition dialog. Changed the code for selecting the size to a separate object that contains a line edit, combo box and slider(replacing the spin box). Aligned the device tree view to right alignment with centered headers like the vgtree. Added support for basic usage of ntfs file systems. version 0.7.4 Added a menu option for removing the filesystem from a logical volume just as it was already possible to do with a partition. version 0.7.3 Added a small docbook handbook and some icons. More bugfixes. The filesystem operations menu now has a dialog for extending a filesystem to fill its underlying volume. version 0.7.2 Snapshots now are nested in the tree like mirror legs. Compressed all the seperate vgchange dialogs into one. Changed the volume group tree widget to keep the same trees expanded or collapsed when the widget gets updated. Added a column to show the metadata areas on the physical volumes and their size. version 0.7.1 Replaced missing include in "lvrename.h" and fixed the "remaining" column in the first tab. It was reporting blocks and not bytes. version 0.7.0 Many internal fixes. LVM now supports resizing mirrors so that was added along with mirrored mirror logs and multi segment mirrors. library lvm2app is now used and required. Btrfs is supported along with volume group splitting and merging. version 0.6.2 Shrinking and growing physical volumes is now supported. Filesystems can be removed from partitions now. Removed the menu item for rescanning only one volume group. It was causing crashes but may be added back when I have time to do it right. version 0.6.1 The filesystem grow and shrink parts of PartMoveResize have been moved out of that object and put into their own files. lv reduce now looks at the minimum shrink size for a filesystem. A simple setup function was added to set the kvpm configuration to something sensible the first time kvpm is run. version 0.6.0 Partitions can now be moved and resized for ext2 and ext3 filesystems. The "fsck" program is now required too. version 0.5.5 The kvpm "settings" dialog can set the column hidden/show for both of the tree views and save it in the config file. version 0.5.4 The volume group creation dialog can be called from the "volume group" menu now and create new groups from multiple physical volumes. version 0.5.3 The settings menu is partly implemented. The search path for the needed programs such as "vgs" can be configured. version 0.5.2 mount points are on the main tree view now. New and blingy "create partition" graphic like "gparted" has to show the size of a partition about to be created along with the free space around it. version 0.5.1 The panels get a more finished look and small areas (under 1 MiB) of free space are not shown anymore. version 0.5.0 Can create a new partition table. Better support for GPT disk labels and a visual make over of the device tab. Needs cmake version 2.6 or better. Adds and removes partitions from partition table. version 0.4.6 Added calls to i18n() for support of translation to other languages. Some bug fixes and work to the mount and unmount operations to allow for overlayed mounting. Help menu for bug reporting improved. version 0.4.5 Added new dialogs to import and export volumes groups. version 0.4.4 The lvproperties widgets have a little bit diffent look now. Small bug fixes. Lvcreate now has a name validator. version 0.4.3 Added more information the the pvproperties widgets. They now use a QTableWidget to show the extents used by each logical volume. Added an new function, lvrename, which does just that. version 0.4.2 Fixed a bug that caused random crashes after mounting/unmounting and certain other operations. The mirror corelog option of lvcreate should work now. version 0.4.1 Still doing coding style unification and hunting down small bugs. Extending non-linear volumes should work better now. version 0.4.0 More additions for handling mirrors. The "properties" widgets improved again and background colors made to match. Many bug fixes. version .3.3 Lots of bug fixes. Volume group information display changed again, much nicer now. version .3.1 Changes to the handling of mirrors, still not done. Copyright notice debugged. Many coding style improvements and bugs fixed. Volume group information display changed but still needs improvement. kvpm-0.9.10/kvpm.desktop0000644000175000017500000000031712770324126015357 0ustar benscottbenscott[Desktop Entry] GenericName=LVM Volume And Partition Manager Name=KVPM Comment=KDE Exec=/usr/sbin/kvpm X-KDE-SubstituteUID=true Type=Application Categories=Qt;KDE;System; Icon=kvpm X-DocPath=kvpm/index.htmlkvpm-0.9.10/kvpm/0000755000175000017500000000000012770324622013764 5ustar benscottbenscottkvpm-0.9.10/kvpm/removemirrorleg.cpp0000644000175000017500000000257612770324126017721 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "removemirrorleg.h" #include #include #include #include "logvol.h" #include "processprogress.h" #include "volgroup.h" bool remove_mirror_leg(LogVol *mirrorLeg) { const QString warning = i18n("Remove mirror leg: %1 ?", mirrorLeg->getName()); if (KMessageBox::warningYesNo(nullptr, warning, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { QStringList args; args << "lvconvert" << "--mirrors" << QString("-1") << mirrorLeg->getParentMirror()->getFullName() << mirrorLeg->getPvNamesAll(); ProcessProgress remove(args); return true; } else { return false; } } kvpm-0.9.10/kvpm/vgextend.h0000644000175000017500000000243112770324126015760 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGEXTEND_H #define VGEXTEND_H #include #include #include #include "kvpmdialog.h" class PvGroupBox; class StorageBase; class VolGroup; class VGExtendDialog : public KvpmDialog { Q_OBJECT PvGroupBox *m_pv_checkbox = nullptr; const VolGroup *const m_vg; QComboBox *m_copies_combo; QLineEdit *m_size_edit; QLineEdit *m_align_edit; QLineEdit *m_offset_edit; bool continueWarning(); void buildDialog(const QList devices); QWidget *buildGeneralTab(const QList devices); QWidget *buildAdvancedTab(); public: VGExtendDialog(const VolGroup *const group, QWidget *parent = nullptr); VGExtendDialog(const VolGroup *const group, const StorageBase *const device, QWidget *parent = nullptr); private slots: void commit(); void validateOK(); }; #endif kvpm-0.9.10/kvpm/vgrename.h0000644000175000017500000000156712770324126015751 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGRENAME_H #define VGRENAME_H #include "kvpmdialog.h" class QLineEdit; class QRegExpValidator; class VolGroup; class VGRenameDialog : public KvpmDialog { Q_OBJECT VolGroup *m_vg = nullptr; QString m_old_name; QLineEdit *m_new_name = nullptr; QRegExpValidator *m_name_validator = nullptr; public: explicit VGRenameDialog(VolGroup *const group, QWidget *parent = nullptr); private slots: void validateName(QString); void commit(); }; #endif kvpm-0.9.10/kvpm/vgchange.h0000644000175000017500000000242312770324126015717 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGCHANGE_H #define VGCHANGE_H #include #include "kvpmdialog.h" class QComboBox; class QSpinBox; class QRadioButton; class QCheckBox; class QGroupBox; class VolGroup; class PolicyComboBox; class VGChangeDialog : public KvpmDialog { Q_OBJECT VolGroup *m_vg; QRadioButton *m_available_yes, *m_available_no, *m_polling_yes, *m_polling_no; QCheckBox *m_resize, *m_clustered, *m_refresh, *m_uuid; QComboBox *m_extent_size_combo, *m_extent_suffix_combo; QGroupBox *m_limit_box, *m_lvlimit_box, *m_pvlimit_box, *m_available_box, *m_polling_box; QSpinBox *m_max_lvs_spin, *m_max_pvs_spin; PolicyComboBox *m_policy_combo; QStringList arguments(); public: explicit VGChangeDialog(VolGroup *const group, QWidget *parent = nullptr); private slots: void limitExtentSize(const int index); void resetOkButton(); void commit(); }; #endif kvpm-0.9.10/kvpm/maxfs.cpp0000644000175000017500000001145212770324126015610 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "maxfs.h" #include "fsextend.h" #include "logvol.h" #include "processprogress.h" #include "physvol.h" #include "pvextend.h" #include "storagedevice.h" #include "storagepartition.h" #include #include #include bool max_fs(LogVol *const logicalVolume) { const QString path = logicalVolume->getMapperPath(); const QString fs = logicalVolume->getFilesystem(); QString full_name = logicalVolume->getFullName(); full_name.remove('[').remove(']'); const QString warning = i18n("Extend the filesystem on: %1 to fill the entire volume?", "" + full_name + ""); const QString error_message1 = i18n("'ntfs' filesystem must be unmounted before extending."); const QString error_message2 = i18n("Extending is only supported for ext2, ext3, ext4, jfs, xfs, ntfs and Reiserfs. " "The correct executables for file system extension must also be present."); if (logicalVolume->isMounted() && ("ntfs" == fs)) { KMessageBox::sorry(nullptr, error_message1); return false; } else if (!fs_can_extend(fs, logicalVolume->isMounted())) { KMessageBox::sorry(nullptr, error_message2); return false; } if (KMessageBox::warningYesNo(nullptr, warning, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { return fs_extend(path, fs, logicalVolume->getMountPoints(), true); } else { return false; } } bool max_fs(StoragePartition *const partition) { const QString path = partition->getName(); const QString fs = partition->getFilesystem(); const QString error_fs = i18n("Filesystem extending is only supported for ext2, ext3, ext4, jfs, xfs, ntfs and Reiserfs. " "Physical volumes can also be extended. " "The correct executables for file system extension must be present"); const QString error_active = i18n("Physical volumes cannot be extended if they contain any active logical volumes"); QString message; if (partition->isPhysicalVolume()) { if (partition->getPhysicalVolume()->isActive()) { KMessageBox::sorry(nullptr, error_active); return false; } else { message = i18n("Extend the physical volume on: %1 to fill the entire partition?", "" + path + ""); } } else { message = i18n("Extend the filesystem on: %1 to fill the entire partition?", "" + path + ""); } if (!(fs_can_extend(fs, partition->isMounted()) || partition->isPhysicalVolume())) { KMessageBox::sorry(nullptr, error_fs); return false; } if (KMessageBox::warningYesNo(nullptr, message, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { if (partition->isPhysicalVolume()) { return pv_extend(path); } else { return fs_extend(path, fs, partition->getMountPoints(), false); } } return false; } bool max_fs(StorageDevice *const device) { const QString path = device->getName(); const QString warning = i18n("Extend the physical volume on: %1 to fill the entire partition?", "" + path + ""); const QString error_active = i18n("Physical volumes cannot be extended if they contain any active logical volumes"); if (!device->isPhysicalVolume()) { return false; } else if (device->getPhysicalVolume()->isActive()) { KMessageBox::sorry(nullptr, error_active); return false; } if (KMessageBox::warningYesNo(nullptr, warning, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { return pv_extend(path); } return false; } kvpm-0.9.10/kvpm/partflag.cpp0000644000175000017500000002571312770324126016277 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "partflag.h" #include "pedexceptions.h" #include "processprogress.h" #include "progressbox.h" #include "storagepartition.h" #include #include #include #include #include #include #include #include PartitionFlagDialog::PartitionFlagDialog(StoragePartition *const partition, QWidget *parent) : KDialog(parent), m_storage_part(partition) { setButtons(KDialog::Ok | KDialog::Cancel | KDialog::Reset); setCaption((i18n("Set Partition Flags"))); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); QLabel *const label = new QLabel(i18n("Set Partition Flags")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); layout->addSpacing(10); QGroupBox *const check_group = new QGroupBox(); QVBoxLayout *const check_layout = new QVBoxLayout(); check_group->setLayout(check_layout); m_boot_check = new QCheckBox("boot"); m_hidden_check = new QCheckBox("hidden"); m_legacy_boot_check = new QCheckBox("legacy_boot"); m_root_check = new QCheckBox("root"); m_swap_check = new QCheckBox("swap"); m_raid_check = new QCheckBox("raid"); m_lvm_check = new QCheckBox("lvm"); m_lba_check = new QCheckBox("lba"); m_palo_check = new QCheckBox("palo"); m_prep_check = new QCheckBox("prep"); m_msftres_check = new QCheckBox("msftres"); m_bios_grub_check = new QCheckBox("bios_grub"); m_atvrecv_check = new QCheckBox("atvrecv"); m_diag_check = new QCheckBox("diag"); m_hp_service_check = new QCheckBox("hp-service"); m_bg = new QButtonGroup(this); m_bg->setExclusive(false); check_layout->addWidget(m_boot_check); check_layout->addWidget(m_hidden_check); check_layout->addWidget(m_legacy_boot_check); check_layout->addWidget(m_root_check); check_layout->addWidget(m_swap_check); check_layout->addWidget(m_raid_check); check_layout->addWidget(m_lvm_check); check_layout->addWidget(m_lba_check); check_layout->addWidget(m_palo_check); check_layout->addWidget(m_prep_check); check_layout->addWidget(m_msftres_check); check_layout->addWidget(m_bios_grub_check); check_layout->addWidget(m_atvrecv_check); check_layout->addWidget(m_diag_check); check_layout->addWidget(m_hp_service_check); if (!m_storage_part) { m_bailout = true; } else if (m_storage_part->isFreespace()) { m_bailout = true; } else if (m_storage_part->isExtended()) { m_bailout = true; } else { m_bailout = false; QStringList available; const PedPartition *const part = m_storage_part->getPedPartition(); PedPartitionFlag flag = (PedPartitionFlag)0; while ( (flag = ped_partition_flag_next(flag)) ) { if( ped_partition_is_flag_available(part, flag) ) { available << QString(ped_partition_flag_get_name(flag)).trimmed(); } } if (QString(part->disk->type->name).trimmed() == QString("gpt")) m_bg->addButton(m_boot_check); m_bg->addButton(m_root_check); m_bg->addButton(m_swap_check); m_bg->addButton(m_raid_check); m_bg->addButton(m_lvm_check); m_bg->addButton(m_lba_check); m_bg->addButton(m_hp_service_check); m_bg->addButton(m_palo_check); m_bg->addButton(m_prep_check); m_bg->addButton(m_msftres_check); m_bg->addButton(m_bios_grub_check); m_bg->addButton(m_atvrecv_check); m_bg->addButton(m_diag_check); m_boot_check->setVisible(available.contains("boot")); m_root_check->setVisible(available.contains("root")); m_swap_check->setVisible(available.contains("swap")); m_raid_check->setVisible(available.contains("raid")); m_hidden_check->setVisible(available.contains("hidden")); m_legacy_boot_check->setVisible(available.contains("legacy_boot")); m_lvm_check->setVisible(available.contains("lvm")); m_lba_check->setVisible(available.contains("lba")); m_hp_service_check->setVisible(available.contains("hp-service")); m_palo_check->setVisible(available.contains("palo")); m_prep_check->setVisible(available.contains("prep")); m_msftres_check->setVisible(available.contains("msftres")); m_bios_grub_check->setVisible(available.contains("bios_grub")); m_atvrecv_check->setVisible(available.contains("atvrecv")); m_diag_check->setVisible(available.contains("diag")); setChecks(); } connect(this, SIGNAL(okClicked()), this, SLOT(commit())); connect(this, SIGNAL(resetClicked()), this, SLOT(setChecks())); connect(m_bg, SIGNAL(buttonClicked(QAbstractButton *)), this, SLOT(makeExclusive(QAbstractButton *))); layout->addWidget(check_group); dialog_body->setLayout(layout); } bool PartitionFlagDialog::bailout() { return m_bailout; } void PartitionFlagDialog::makeExclusive(QAbstractButton *button) { bool checked = button->isChecked(); const QList list(m_bg->buttons()); for (int x = 0; x < list.size(); x++) list[x]->setChecked(false); button->setChecked(checked); } void PartitionFlagDialog::setChecks() { const PedPartition *const part = m_storage_part->getPedPartition(); m_boot_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("boot"))); m_root_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("root"))); m_swap_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("swap"))); m_legacy_boot_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("legacy_boot"))); m_hidden_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("hidden"))); m_raid_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("raid"))); m_lvm_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("lvm"))); m_lba_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("lba"))); m_hp_service_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("hp-service"))); m_palo_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("palo"))); m_prep_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("prep"))); m_msftres_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("msftres"))); m_bios_grub_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("bios_grub"))); m_atvrecv_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("atvrecv"))); m_diag_check->setChecked(ped_partition_get_flag(part, ped_partition_flag_get_by_name("diag"))); } void PartitionFlagDialog::commit() { PedPartition *const part = m_storage_part->getPedPartition(); if (m_boot_check->isVisible()) { if (m_boot_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_BOOT, 1); else ped_partition_set_flag(part, PED_PARTITION_BOOT, 0); } if (m_root_check->isVisible()) { if (m_root_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_ROOT, 1); else ped_partition_set_flag(part, PED_PARTITION_ROOT, 0); } if (m_swap_check->isVisible()) { if (m_swap_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_SWAP, 1); else ped_partition_set_flag(part, PED_PARTITION_SWAP, 0); } if (m_hidden_check->isVisible()) { if (m_hidden_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_HIDDEN, 1); else ped_partition_set_flag(part, PED_PARTITION_HIDDEN, 0); } if (m_legacy_boot_check->isVisible()) { if (m_legacy_boot_check->isChecked()) ped_partition_set_flag(part, ped_partition_flag_get_by_name("legacy_boot"), 1); else ped_partition_set_flag(part, ped_partition_flag_get_by_name("legacy_boot"), 0); } if (m_raid_check->isVisible()) { if (m_raid_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_RAID, 1); else ped_partition_set_flag(part, PED_PARTITION_RAID, 0); } if (m_lvm_check->isVisible()) { if (m_lvm_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_LVM, 1); else ped_partition_set_flag(part, PED_PARTITION_LVM, 0); } if (m_lba_check->isVisible()) { if (m_lba_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_LBA, 1); else ped_partition_set_flag(part, PED_PARTITION_LBA, 0); } if (m_hp_service_check->isVisible()) { if (m_hp_service_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_HPSERVICE, 1); else ped_partition_set_flag(part, PED_PARTITION_HPSERVICE, 0); } if (m_palo_check->isVisible()) { if (m_palo_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_PALO, 1); else ped_partition_set_flag(part, PED_PARTITION_PALO, 0); } if (m_prep_check->isVisible()) { if (m_prep_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_PREP, 1); else ped_partition_set_flag(part, PED_PARTITION_PREP, 0); } if (m_msftres_check->isVisible()) { if (m_msftres_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_MSFT_RESERVED, 1); else ped_partition_set_flag(part, PED_PARTITION_MSFT_RESERVED, 0); } if (m_bios_grub_check->isVisible()) { if (m_bios_grub_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_BIOS_GRUB, 1); else ped_partition_set_flag(part, PED_PARTITION_BIOS_GRUB, 0); } if (m_atvrecv_check->isVisible()) { if (m_atvrecv_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_APPLE_TV_RECOVERY, 1); else ped_partition_set_flag(part, PED_PARTITION_APPLE_TV_RECOVERY, 0); } if (m_diag_check->isVisible()) { if (m_diag_check->isChecked()) ped_partition_set_flag(part, PED_PARTITION_DIAG, 1); else ped_partition_set_flag(part, PED_PARTITION_DIAG, 0); } ped_disk_commit(part->disk); } kvpm-0.9.10/kvpm/lvactions.cpp0000644000175000017500000011067512770324126016503 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvactions.h" #include "fsck.h" #include "logvol.h" #include "vgtree.h" #include "lvsizechartseg.h" #include "topwindow.h" #include "changemirror.h" #include "lvsizechartseg.h" #include "lvcreate.h" #include "lvchange.h" #include "lvreduce.h" #include "lvremove.h" #include "lvrename.h" #include "mkfs.h" #include "maxfs.h" #include "mount.h" #include "pvmove.h" #include "repairmissing.h" #include "removemirror.h" #include "removemirrorleg.h" #include "resync.h" #include "snapmerge.h" #include "thincreate.h" #include "unmount.h" #include "volgroup.h" #include #include #include LVActions::LVActions(VolGroup *const group, QWidget *parent) : KActionCollection(parent), m_vg(group) { QAction *const lv_create = addAction("lvcreate"); lv_create->setText(i18n("Create logical volume...")); lv_create->setIconText(i18n("New volume")); lv_create->setToolTip(i18n("Create a new logical volume")); lv_create->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); QAction *const thin_pool = addAction("thinpool"); thin_pool->setText(i18n("Create thin pool...")); thin_pool->setToolTip(i18n("Create a new thin pool")); thin_pool->setIconText(i18n("New pool")); thin_pool->setIcon(QIcon::fromTheme(QStringLiteral("object-group"))); QAction *const thin_create = addAction("thincreate"); thin_create->setText(i18n("Create thin volume...")); thin_create->setToolTip(i18n("Create new thin volume")); thin_create->setIconText(i18n("Thin volume")); thin_create->setIcon(QIcon::fromTheme(QStringLiteral("page_white_add"))); QAction *const lv_rename = addAction("lvrename"); lv_rename->setText(i18n("Rename logical volume...")); lv_rename->setToolTip(i18n("Rename a logical volume or thin pool")); lv_rename->setIconText(i18n("Rename")); lv_rename->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); QAction *const snap_create = addAction("snapcreate"); snap_create->setText(i18n("Create snapshot...")); snap_create->setToolTip(i18n("Create a snapshot of a volume")); snap_create->setIconText(i18n("Snapshot")); snap_create->setIcon(QIcon::fromTheme(QStringLiteral("camera_add"))); QAction *const thin_snap = addAction("thinsnap"); thin_snap->setText(i18n("Create thin snapshot...")); thin_snap->setToolTip(i18n("Create a thin snapshot of a volume")); thin_snap->setText(i18n("Thin snapshot")); thin_snap->setIcon(QIcon::fromTheme(QStringLiteral("page_white_camera"))); QAction *const snap_merge = addAction("snapmerge"); snap_merge->setText(i18n("Merge snapshot...")); snap_merge->setToolTip(i18n("Merge a snapshot with its origin")); snap_merge->setIconText(i18n("Merge")); snap_merge->setIcon(QIcon::fromTheme(QStringLiteral("arrow_join"))); QAction *const lv_reduce = addAction("lvreduce"); lv_reduce->setText(i18n("Reduce logical volume...")); lv_reduce->setToolTip(i18n("Reduce the size of a logical volume or thin pool")); lv_reduce->setText(i18n("Reduce")); lv_reduce->setIcon(QIcon::fromTheme(QStringLiteral("delete"))); QAction *const lv_extend = addAction("lvextend"); lv_extend->setText(i18n("Extend logical volume...")); lv_extend->setToolTip(i18n("Increase the size of a logical volume or thin pool")); lv_extend->setIconText(i18n("Extend")); lv_extend->setIcon(QIcon::fromTheme(QStringLiteral("add"))); QAction *const pv_move = addAction("pvmove"); pv_move->setText(i18n("Move physical extents...")); pv_move->setToolTip(i18n("Move extents to another physical volume")); pv_move->setIconText(i18n("Move")); pv_move->setIcon(QIcon::fromTheme(QStringLiteral("lorry"))); QAction *const lv_change = addAction("lvchange"); lv_change->setText(i18n("Change attributes or tags...")); lv_change->setToolTip(i18n("Change attributes or tags of a volume or thin pool")); lv_change->setIconText(i18n("Attributes")); lv_change->setIcon(QIcon::fromTheme(QStringLiteral("wrench"))); QAction *const lv_remove = addAction("lvremove"); lv_remove->setText(i18n("Delete logical volume...")); lv_remove->setToolTip(i18n("Delete logical volume or thin pool")); lv_remove->setIconText(i18n("Delete")); lv_remove->setIcon(QIcon::fromTheme(QStringLiteral("cross"))); QAction *const mkfs = addAction("mkfs"); mkfs->setIcon(QIcon::fromTheme(QStringLiteral("lightning_add"))); mkfs->setText(i18n("Make or remove filesystem...")); mkfs->setIconText(i18n("mkfs")); mkfs->setToolTip(i18n("Make or remove a filesystem")); QAction *const max_fs = addAction("maxfs"); max_fs->setText(i18n("Extend filesystem to fill volume...")); max_fs->setIcon(QIcon::fromTheme(QStringLiteral("resultset_last"))); max_fs->setIconText(i18n("Extend fs")); max_fs->setToolTip(i18n("Extend filesystem to fill volume")); QAction *const fsck = addAction("fsck"); fsck->setText(i18n("Run 'fsck -fp' on filesystem...")); fsck->setIcon(QIcon::fromTheme(QStringLiteral("checkmark"))); fsck->setIconText(i18n("fsck -fp")); fsck->setToolTip(i18n("Run 'fsck -fp' on the filesystem")); QAction *const mount = addAction("mount"); mount->setText(i18n("Mount filesystem...")); mount->setIconText(i18n("mount fs")); mount->setToolTip(i18n("Mount filesystem")); mount->setIcon(QIcon::fromTheme(QStringLiteral("emblem-mounted"))); QAction *const unmount = addAction("unmount"); unmount->setText(i18n("Unmount filesystem...")); unmount->setIconText(i18n("Unmount fs")); unmount->setToolTip(i18n("Unmount filesystem")); unmount->setIcon(QIcon::fromTheme(QStringLiteral("emblem-unmounted"))); QAction *const add_legs = addAction("addlegs"); add_legs->setText(i18n("Add mirror legs to volume...")); add_legs->setIconText(i18n("Add mirrors")); add_legs->setToolTip(i18n("Add mirror legs to volume")); add_legs->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); QAction *const change_log = addAction("changelog"); change_log->setText(i18n("Change mirror log...")); change_log->setIconText(i18n("Change log")); change_log->setToolTip(i18n("Change mirror log")); change_log->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); QAction *const remove_mirror = addAction("removemirror"); remove_mirror->setText(i18n("Remove mirror legs...")); remove_mirror->setToolTip(i18n("Remove mirror legs")); remove_mirror->setIconText(i18n("Remove legs")); remove_mirror->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); QAction *const remove_this = addAction("removethis"); remove_this->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); remove_this->setText(i18n("Remove this mirror leg...")); remove_this->setIconText(i18n("Remove leg...")); remove_this->setText(i18n("Remove this mirror leg")); QAction *const repair_missing = addAction("repairmissing"); repair_missing->setIcon(QIcon::fromTheme(QStringLiteral("edit-bomb"))); repair_missing->setText(i18n("Repair RAID or mirror...")); repair_missing->setIconText(i18n("Repair")); repair_missing->setToolTip(i18n("Repair RAID or mirror volume")); QAction *const resync = addAction("resync"); resync->setIcon(QIcon::fromTheme(QStringLiteral("arrow_refresh"))); resync->setText(i18n("Resync RAID or mirror...")); resync->setIconText(i18n("Resync")); resync->setToolTip(i18n("Resync RAID or mirror volume")); setActions(nullptr, 0); connect(this, SIGNAL(actionTriggered(QAction *)), this, SLOT(callDialog(QAction *))); } void LVActions::setActions(LogVol *const lv, const int segment) { m_lv = lv; m_segment = segment; QAction *const lv_create = action("lvcreate"); QAction *const thin_pool = action("thinpool"); QAction *const thin_create = action("thincreate"); QAction *const lv_rename = action("lvrename"); QAction *const snap_create = action("snapcreate"); QAction *const thin_snap = action("thinsnap"); QAction *const snap_merge = action("snapmerge"); QAction *const lv_reduce = action("lvreduce"); QAction *const lv_extend = action("lvextend"); QAction *const pv_move = action("pvmove"); QAction *const lv_change = action("lvchange"); QAction *const lv_remove = action("lvremove"); QAction *const mount = action("mount"); QAction *const unmount = action("unmount"); QAction *const max_fs = action("maxfs"); QAction *const fsck = action("fsck"); QAction *const mkfs = action("mkfs"); lv_create->setEnabled(true); thin_pool->setEnabled(true); thin_create->setEnabled(true); lv_rename->setEnabled(true); snap_create->setEnabled(true); thin_snap->setEnabled(true); snap_merge->setEnabled(true); lv_reduce->setEnabled(true); lv_extend->setEnabled(true); pv_move->setEnabled(true); lv_change->setEnabled(true); lv_remove->setEnabled(true); mount->setEnabled(true); unmount->setEnabled(true); max_fs->setEnabled(true); fsck->setEnabled(true); mkfs->setEnabled(true); lv_extend->setText(i18n("Extend logical volume...")); lv_reduce->setText(i18n("Reduce logical volume...")); lv_rename->setText(i18n("Rename logical volume...")); lv_remove->setText(i18n("Delete logical volume...")); if (m_vg->isExported() || m_vg->openFailed()) { lv_create->setEnabled(false); thin_pool->setEnabled(false); thin_create->setEnabled(false); lv_rename->setEnabled(false); snap_create->setEnabled(false); thin_snap->setEnabled(false); snap_merge->setEnabled(false); lv_reduce->setEnabled(false); lv_extend->setEnabled(false); pv_move->setEnabled(false); lv_change->setEnabled(false); lv_remove->setEnabled(false); mount->setEnabled(false); unmount->setEnabled(false); max_fs->setEnabled(false); fsck->setEnabled(false); mkfs->setEnabled(false); } else if (lv) { // snap containers are replaced by the "real" lv before getting here, see: m_vg->getLvByName() thin_create->setEnabled(false); thin_snap->setEnabled(false); if ((lv->isThinSnap() || lv->isCowSnap()) && lv->isValid() && !lv->isMerging()) snap_merge->setEnabled(true); else snap_merge->setEnabled(false); if(lv->isThinPool()){ lv_extend->setText(i18n("Extend thin pool...")); lv_reduce->setText(i18n("Reduce thin pool...")); lv_rename->setText(i18n("Rename thin pool...")); lv_remove->setText(i18n("Delete thin pool...")); lv_create->setEnabled(false); lv_remove->setEnabled(true); thin_create->setEnabled(true); thin_pool->setEnabled(false); snap_merge->setEnabled(false); max_fs->setEnabled(false); mkfs->setEnabled(false); fsck->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); lv_change->setEnabled(true); lv_extend->setEnabled(true); lv_reduce->setEnabled(false); lv_rename->setEnabled(true); pv_move->setEnabled(true); snap_create->setEnabled(false); } else if (lv->isMetadata() && !lv->isThinMetadata() && !lv->isRaidMetadata()) { // thin pool spare metadata mkfs->setEnabled(false); max_fs->setEnabled(false); fsck->setEnabled(false); lv_remove->setEnabled(true); unmount->setEnabled(false); mount->setEnabled(false); lv_change->setEnabled(false); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); lv_rename->setEnabled(false); pv_move->setEnabled(false); snap_create->setEnabled(false); } else if(lv->isThinMetadata() || lv->isThinPoolData()){ thin_create->setEnabled(true); snap_merge->setEnabled(false); lv_remove->setEnabled(false); max_fs->setEnabled(false); mkfs->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); fsck->setEnabled(false); lv_change->setEnabled(false); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); lv_rename->setEnabled(false); if (lv->isLvmMirror()) pv_move->setEnabled(false); else pv_move->setEnabled(true); snap_create->setEnabled(false); } else if(lv->isLvmMirrorLog() || lv->isTemporary()){ snap_merge->setEnabled(false); max_fs->setEnabled(false); mkfs->setEnabled(false); if (lv->getParentMirror() == nullptr) // allow removal of bogus log lv_remove->setEnabled(true); else lv_remove->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); fsck->setEnabled(false); lv_change->setEnabled(false); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); lv_rename->setEnabled(false); pv_move->setEnabled(false); snap_create->setEnabled(false); } else if (lv->isWritable() && !lv->isLocked() && !lv->isVirtual() && !lv->isThinVolume() && !lv->isMirrorLeg() && !lv->isLvmMirrorLog() && !lv->isRaidImage() && !lv->isTemporary() && !lv->isRaidMetadata()) { if (lv->isMounted()) { fsck->setEnabled(false); mkfs->setEnabled(false); lv_reduce->setEnabled(false); lv_extend->setEnabled(true); lv_remove->setEnabled(false); unmount->setEnabled(true); mount->setEnabled(true); } else if (lv->isOpen() && lv->getFilesystem() == "swap") { fsck->setEnabled(false); mkfs->setEnabled(false); lv_reduce->setEnabled(false); lv_extend->setEnabled(false); lv_remove->setEnabled(false); max_fs->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); } else { mkfs->setEnabled(true); lv_reduce->setEnabled(true); lv_extend->setEnabled(true); lv_remove->setEnabled(true); unmount->setEnabled(false); mount->setEnabled(true); } if (lv->isCowOrigin()) { snap_create->setEnabled(true); if (lv->isMerging()) { snap_create->setEnabled(false); lv_extend->setEnabled(false); } else { lv_extend->setEnabled(true); } lv_reduce->setEnabled(false); pv_move->setEnabled(false); } else if (lv->isCowSnap()) { max_fs->setEnabled(false); snap_create->setEnabled(false); pv_move->setEnabled(false); if (lv->isMerging() || !lv->isValid()) { lv_extend->setEnabled(false); lv_reduce->setEnabled(false); mount->setEnabled(false); fsck->setEnabled(false); mkfs->setEnabled(false); if (!lv->isValid()) lv_remove->setEnabled(true); else lv_remove->setEnabled(false); } else if (lv->isMounted()) { lv_extend->setEnabled(true); lv_reduce->setEnabled(false); fsck->setEnabled(false); mkfs->setEnabled(false); } else { lv_extend->setEnabled(true); lv_reduce->setEnabled(true); fsck->setEnabled(true); mkfs->setEnabled(true); } } else if (lv->isLvmMirror()) { pv_move->setEnabled(false); if (lv->isUnderConversion()) { lv_extend->setEnabled(false); lv_reduce->setEnabled(false); } else { lv_extend->setEnabled(true); lv_reduce->setEnabled(true); } } else if (lv->isRaid()) { lv_change->setEnabled(false); lv_extend->setEnabled(true); lv_reduce->setEnabled(false); lv_rename->setEnabled(true); pv_move->setEnabled(true); snap_create->setEnabled(true); } else { snap_create->setEnabled(true); } if (lv->isCowSnap() && lv->isMerging()) { lv_rename->setEnabled(false); lv_change->setEnabled(false); } else { lv_rename->setEnabled(true); lv_change->setEnabled(true); } } else if (lv->isThinVolume()){ lv_extend->setText(i18n("Extend thin volume...")); lv_reduce->setText(i18n("Reduce thin volume...")); lv_rename->setText(i18n("Rename thin volume...")); lv_remove->setText(i18n("Delete thin volume...")); thin_create->setEnabled(true); thin_snap->setEnabled(true); pv_move->setEnabled(false); lv_reduce->setEnabled(true); if (lv->isMounted()) { fsck->setEnabled(false); mkfs->setEnabled(false); lv_reduce->setEnabled(false); lv_extend->setEnabled(true); lv_remove->setEnabled(false); unmount->setEnabled(true); mount->setEnabled(true); } else if (lv->isOpen() && lv->getFilesystem() == "swap") { fsck->setEnabled(false); mkfs->setEnabled(false); lv_reduce->setEnabled(false); lv_extend->setEnabled(false); lv_remove->setEnabled(false); max_fs->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); } else { mkfs->setEnabled(true); lv_extend->setEnabled(true); lv_remove->setEnabled(true); unmount->setEnabled(false); mount->setEnabled(true); } if (!lv->isWritable()) { fsck->setEnabled(false); mkfs->setEnabled(false); lv_reduce->setEnabled(false); lv_extend->setEnabled(false); max_fs->setEnabled(false); } else if (lv->isCowOrigin()) { lv_reduce->setEnabled(false); } } else if (lv->isOrphan()) { mkfs->setEnabled(false); max_fs->setEnabled(false); lv_remove->setEnabled(true); unmount->setEnabled(false); mount->setEnabled(false); fsck->setEnabled(false); lv_change->setEnabled(false); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); lv_rename->setEnabled(false); pv_move->setEnabled(false); snap_create->setEnabled(false); } else if (lv->isRaidImage() || lv->isRaidMetadata()) { mkfs->setEnabled(false); max_fs->setEnabled(false); fsck->setEnabled(false); lv_remove->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); lv_change->setEnabled(false); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); lv_rename->setEnabled(false); pv_move->setEnabled(true); snap_create->setEnabled(false); } else if (lv->isPvmove()) { mkfs->setEnabled(false); max_fs->setEnabled(false); fsck->setEnabled(false); lv_remove->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); lv_change->setEnabled(false); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); lv_rename->setEnabled(false); pv_move->setEnabled(false); snap_create->setEnabled(false); } else if (lv->isLvmMirrorLeg() && !lv->isLvmMirrorLog() && !lv->isTemporary()) { mkfs->setEnabled(false); max_fs->setEnabled(false); fsck->setEnabled(false); lv_remove->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); lv_change->setEnabled(false); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); pv_move->setEnabled(false); lv_rename->setEnabled(false); snap_create->setEnabled(false); } else if (!(lv->isWritable()) && lv->isLocked()) { if (lv->isMounted()) unmount->setEnabled(true); else unmount->setEnabled(false); mount->setEnabled(true); mkfs->setEnabled(false); max_fs->setEnabled(false); lv_remove->setEnabled(false); lv_change->setEnabled(true); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); lv_rename->setEnabled(false); pv_move->setEnabled(false); snap_create->setEnabled(false); } else if (lv->isWritable() && lv->isLocked()) { if (lv->isMounted()) unmount->setEnabled(true); else unmount->setEnabled(false); mount->setEnabled(true); mkfs->setEnabled(true); lv_remove->setEnabled(false); lv_rename->setEnabled(false); lv_change->setEnabled(true); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); pv_move->setEnabled(false); snap_create->setEnabled(false); } else { if (lv->isMounted()) { lv_remove->setEnabled(false); unmount->setEnabled(true); } else { lv_remove->setEnabled(true); unmount->setEnabled(false); } if (lv->isCowSnap() || lv->isCowOrigin()) { pv_move->setEnabled(false); if (lv->isCowSnap()) snap_create->setEnabled(false); else snap_create->setEnabled(true); } else if (lv->isMirror()) { pv_move->setEnabled(false); snap_create->setEnabled(false); } else { pv_move->setEnabled(true); snap_create->setEnabled(true); } mkfs->setEnabled(false); fsck->setEnabled(false); max_fs->setEnabled(false); lv_reduce->setEnabled(false); lv_extend->setEnabled(false); if (lv->isVirtual()) { lv_rename->setEnabled(false); lv_remove->setEnabled(false); mount->setEnabled(false); lv_change->setEnabled(false); } else { mount->setEnabled(true); lv_change->setEnabled(true); } } if (!lv->isActive()) { mkfs->setEnabled(false); fsck->setEnabled(false); max_fs->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); snap_create->setEnabled(false); } } else { snap_merge->setEnabled(false); max_fs->setEnabled(false); mkfs->setEnabled(false); lv_remove->setEnabled(false); unmount->setEnabled(false); mount->setEnabled(false); fsck->setEnabled(false); lv_change->setEnabled(false); lv_extend->setEnabled(false); lv_reduce->setEnabled(false); lv_rename->setEnabled(false); pv_move->setEnabled(false); snap_create->setEnabled(false); thin_snap->setEnabled(false); thin_create->setEnabled(false); } } void LVActions::setMirrorActions(LogVol *const lv) { bool partial = false; QAction *const add_legs = action("addlegs"); QAction *const remove_mirror = action("removemirror"); QAction *const change_log = action("changelog"); QAction *const remove_this = action("removethis"); QAction *const repair_missing = action("repairmissing"); QAction *const resync = action("resync"); add_legs->setEnabled(true); change_log->setEnabled(true); remove_mirror->setEnabled(true); remove_this->setEnabled(true); repair_missing->setEnabled(true); resync->setEnabled(false); if (lv && !m_vg->isExported()) { if ((lv->isRaidImage() || lv->isRaidMetadata()) && (lv->getParent())) partial = lv->getParent()->isPartial(); else if ((lv->isLvmMirrorLog() || lv->isLvmMirrorLeg() || lv->isTemporary()) && (lv->getParentMirror())) partial = lv->getParentMirror()->isPartial(); else partial = lv->isPartial(); if ((lv->isMirror() || lv->isRaid()) && !lv->isPvmove()) { if (!(lv->isRaidImage() || lv->isRaidMetadata() || lv->isMirrorLeg() || lv->isLvmMirrorLog() || lv->isTemporary())) resync->setEnabled(true); } if (lv->isThinPool()) { add_legs->setEnabled(false); remove_mirror->setEnabled(false); change_log->setEnabled(false); remove_this->setEnabled(false); repair_missing->setEnabled(false); } else if (lv->isMetadata() && !lv->isThinMetadata() && !lv->isRaidMetadata()) { add_legs->setEnabled(false); remove_mirror->setEnabled(false); change_log->setEnabled(false); remove_this->setEnabled(false); repair_missing->setEnabled(false); } else if (partial && (lv->isRaidImage() || lv->isRaid() || lv->isMirror() || lv->isRaidMetadata() || lv->isMirrorLeg() || lv->isLvmMirrorLog() || lv->isTemporary())) { add_legs->setEnabled(false); remove_mirror->setEnabled(false); change_log->setEnabled(false); remove_this->setEnabled(false); repair_missing->setEnabled(true); } else if (partial) { add_legs->setEnabled(false); remove_mirror->setEnabled(false); change_log->setEnabled(false); remove_this->setEnabled(false); repair_missing->setEnabled(false); } else if(lv->isThinMetadata() || lv->isThinPoolData()){ add_legs->setEnabled(false); remove_mirror->setEnabled(false); change_log->setEnabled(false); remove_this->setEnabled(false); repair_missing->setEnabled(false); } else if(lv->isLvmMirrorLog() || lv->isTemporary()){ remove_mirror->setEnabled(true); change_log->setEnabled(true); remove_this->setEnabled(false); repair_missing->setEnabled(false); if (lv->getParentMirror()) { if (lv->getParentMirror()->isUnderConversion() || lv->getParentMirror()->isCowOrigin()) add_legs->setEnabled(false); else add_legs->setEnabled(true); } else { add_legs->setEnabled(false); } } else if (lv->isRaidImage()) { remove_mirror->setEnabled(false); change_log->setEnabled(false); repair_missing->setEnabled(false); if(lv->isMirrorLeg()) { remove_this->setEnabled(true); remove_this->setVisible(true); remove_mirror->setVisible(false); add_legs->setEnabled(true); } else { remove_this->setEnabled(false); add_legs->setEnabled(false); } } else if (lv->isRaidMetadata()) { remove_mirror->setEnabled(false); change_log->setEnabled(false); repair_missing->setEnabled(false); remove_this->setEnabled(false); if (lv->getParent()) { if (lv->getParent()->isMirror()) add_legs->setEnabled(true); else add_legs->setEnabled(false); } else { add_legs->setEnabled(false); } } else if (!lv->isLocked() && !lv->isVirtual() && !lv->isThinVolume() && !lv->isMirrorLeg() && !lv->isLvmMirrorLog() && !lv->isRaidImage() && !lv->isTemporary()) { remove_this->setEnabled(false); repair_missing->setEnabled(false); if (lv->isCowOrigin()) { if (lv->isMirror()) { if (lv->isRaid()) { change_log->setEnabled(false); add_legs->setEnabled(true); } else { change_log->setEnabled(true); add_legs->setEnabled(false); } remove_mirror->setEnabled(true); } else if (lv->isRaid()) { add_legs->setEnabled(false); change_log->setEnabled(false); remove_mirror->setEnabled(false); } else { add_legs->setEnabled(true); change_log->setEnabled(false); remove_mirror->setEnabled(false); } if (lv->isMerging()) { add_legs->setEnabled(false); } } else if (lv->isCowSnap()) { add_legs->setEnabled(false); remove_mirror->setEnabled(false); change_log->setEnabled(false); } else if (lv->isMirror()) { remove_mirror->setEnabled(true); if (lv->isRaid()) change_log->setEnabled(false); else change_log->setEnabled(true); if (lv->isUnderConversion()) add_legs->setEnabled(false); else add_legs->setEnabled(true); } else if (lv->isRaid()) { add_legs->setEnabled(false); change_log->setEnabled(false); remove_mirror->setEnabled(false); } else { add_legs->setEnabled(true); remove_mirror->setEnabled(false); change_log->setEnabled(false); } } else if (lv->isVirtual() ||lv->isThinVolume() || lv->isOrphan() || lv->isPvmove()) { add_legs->setEnabled(false); remove_mirror->setEnabled(false); change_log->setEnabled(false); remove_this->setEnabled(false); repair_missing->setEnabled(false); } else if (lv->isLvmMirrorLeg() && !lv->isLvmMirrorLog() && !lv->isTemporary()) { remove_mirror->setEnabled(false); change_log->setEnabled(true); remove_this->setEnabled(true); remove_this->setVisible(true); remove_mirror->setVisible(false); repair_missing->setEnabled(false); if (lv->getParentMirror()) { if (lv->getParentMirror()->isUnderConversion() || lv->getParentMirror()->isCowOrigin()) add_legs->setEnabled(false); else add_legs->setEnabled(true); } else { add_legs->setEnabled(false); } } else if (lv->isLocked()) { remove_mirror->setEnabled(false); change_log->setEnabled(false); remove_this->setEnabled(false); add_legs->setEnabled(false); repair_missing->setEnabled(false); } if (!lv->isActive()) { change_log->setEnabled(false); } } else { add_legs->setEnabled(false); remove_mirror->setEnabled(false); change_log->setEnabled(false); remove_this->setEnabled(false); repair_missing->setEnabled(false); } remove_this->setVisible(remove_this->isEnabled()); remove_mirror->setVisible(!remove_this->isEnabled()); } void LVActions::changeLv(LogVol *lv, int segment) { setActions(lv, segment); // segment = -1 means whole lv setMirrorActions(lv); } void LVActions::changeLv(QTreeWidgetItem *item) { LogVol *target = nullptr; int segment = -1; if (item) { for (auto lv : m_vg->getLogicalVolumesFlat()) { if (QVariant(item->data(2, Qt::UserRole)).toString() == lv->getUuid()) { target = lv; if (QVariant(item->data(3, Qt::UserRole)).toString() == "segment") segment = QVariant(item->data(1, Qt::UserRole)).toInt(); break; } } } changeLv(target, segment); } void LVActions::callDialog(QAction *action) { const QString name = action->objectName(); if (name == "snapmerge") { if (merge_snap(m_lv)) g_top_window->reRun(); } else if (name == "maxfs") { if (max_fs(m_lv)) g_top_window->reRun(); } else if (name == "fsck") { if (manual_fsck(m_lv)) g_top_window->reRun(); } else if (name == "removethis") { if (remove_mirror_leg(m_lv)) g_top_window->reRun(); } else if (name == "resync") { if (resync(m_lv)) g_top_window->reRun(); } else { KvpmDialog *dialog = nullptr; if (name == "mount") dialog = new MountDialog(m_lv); else if (name == "unmount") dialog = new UnmountDialog(m_lv); else if (name == "mkfs") dialog = new MkfsDialog(m_lv); else if (name == "lvcreate") dialog = new LVCreateDialog(m_vg, false); else if (name == "lvremove") dialog = new LVRemoveDialog(m_lv); else if (name == "thinpool") dialog = new LVCreateDialog (m_vg, true); else if (name == "lvrename") dialog = new LVRenameDialog(m_lv); else if (name == "lvreduce") dialog = new LVReduceDialog(m_lv); else if (name == "snapcreate") dialog = new LVCreateDialog(m_lv, true); else if (name == "thinsnap") dialog = new ThinCreateDialog(m_lv, true); else if (name == "lvchange") dialog = new LVChangeDialog(m_lv); else if (name == "pvmove") dialog = new PVMoveDialog(m_lv, m_segment); else if (name == "addlegs") dialog = new ChangeMirrorDialog(m_lv, false); else if (name == "changelog") dialog = new ChangeMirrorDialog(m_lv, true); else if (name == "removemirror") dialog = new RemoveMirrorDialog(m_lv); else if (name == "repairmissing") dialog = new RepairMissingDialog(m_lv); else if (name == "thincreate") { auto pool = m_lv; while (pool->getParent() && !pool->isThinPool()) pool = pool->getParent(); dialog = new ThinCreateDialog(pool); } else if (name == "lvextend") { if (m_lv->isThinVolume()) dialog = new ThinCreateDialog(m_lv, false); else dialog = new LVCreateDialog(m_lv, false); } if (dialog) { const int result = dialog->run(); if (result == QDialog::Accepted || result == KDialog::Yes) g_top_window->reRun(); } } } kvpm-0.9.10/kvpm/CMakeLists.txt0000644000175000017500000000432112770324126016523 0ustar benscottbenscott set(kvpm_SRCS allocationpolicy.cpp changemirror.cpp kvpmconfigdialog.cpp deviceproperties.cpp devicepropertiesstack.cpp devicesizechart.cpp devicesizechartseg.cpp externalraid.cpp devicetree.cpp devicetab.cpp executablefinder.cpp devicemenu.cpp deviceactions.cpp fsck.cpp fsblocksize.cpp fsdata.cpp fsextend.cpp dualselectorbox.cpp fsprobe.cpp fsreduce.cpp kvpmdialog.cpp lvactions.cpp logvol.cpp lvactionsmenu.cpp lvchange.cpp lvrename.cpp lvcreate.cpp lvcreatebase.cpp lvproperties.cpp lvpropertiesstack.cpp lvreduce.cpp lvremove.cpp lvsizechart.cpp lvmconfig main.cpp maxfs.cpp mkfs.cpp masterlist.cpp mount.cpp lvsizechartseg.cpp mountentry.cpp mounttables.cpp maintabwidget.cpp pvextend.cpp pvreduce.cpp partremove.cpp pvgroupbox.cpp partbase.cpp partitiongraphic.cpp partadd.cpp pvproperties.cpp pvpropertiesstack.cpp physvol.cpp partchange.cpp progressbox.cpp repairmissing.cpp partflag.cpp pvactions.cpp pvactionsmenu.cpp resync.cpp misc.cpp pedexceptions.cpp processprogress.cpp pvchange.cpp snapmerge.cpp pvmove.cpp pvtree.cpp vgremovemissing.cpp removemirror.cpp sizeselectorbox.cpp removemirrorleg.cpp storagepartition.cpp storagedevice.cpp storagebase.cpp thincreate.cpp topwindow.cpp tablecreate.cpp unmount.cpp vgchange.cpp vgexport.cpp vgimport.cpp vgcreate.cpp vgextend.cpp vginfolabels.cpp vgactions.cpp vgreduce.cpp vgremove.cpp vgtree.cpp volgroup.cpp vgwarning.cpp vgrename.cpp volumegrouptab.cpp vgsplit.cpp vgmerge.cpp) add_executable(kvpm ${kvpm_SRCS}) target_link_libraries(kvpm KF5::CoreAddons KF5::I18n KF5::KDELibs4Support blkid parted lvm2app devmapper) INSTALL( TARGETS kvpm DESTINATION ${SBIN_INSTALL_DIR} ) set_target_properties(kvpm PROPERTIES COMPILE_FLAGS "-Wall -Wno-psabi -Wno-cast-align -Wstrict-aliasing") set_target_properties(kvpm PROPERTIES LINK_FLAGS "-Wl,--as-needed") IF(CMAKE_COMPILER_IS_GNUCXX) SET(CMAKE_CXX_FLAGS "-std=gnu++11") SET(CMAKE_C_FLAGS "-std=gnu99") ELSE() SET(CMAKE_CXX_FLAGS "-std=c++11") SET(CMAKE_C_FLAGS "-std=c99") ENDIF(CMAKE_COMPILER_IS_GNUCXX) SET(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-ggdb -O2") kvpm-0.9.10/kvpm/devicetree.h0000644000175000017500000000401012770324126016246 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef DEVICETREE_H #define DEVICETREE_H #include #include #include #include #include class StorageBase; class StorageDevice; class StoragePartition; class DeviceSizeChart; class DevicePropertiesStack; class DeviceTree : public QTreeWidget { Q_OBJECT bool m_initial_run, m_show_total, m_show_percent, m_show_both, m_expand_parts, m_use_si_units; int m_fs_warn_percent, m_pv_warn_percent; DeviceSizeChart *m_chart; DevicePropertiesStack *m_stack; void currentItemNames(QString ¤t, QString ¤tParent); void expandedItemNames(QStringList &expanded, QStringList &old); void expandItem(QTreeWidgetItem *const item, const QStringList expanded, const QStringList old); QTreeWidgetItem *buildDeviceItem(StorageDevice *const dev); QTreeWidgetItem *buildPartitionItem(StoragePartition *const part, StorageDevice *const dev); void setItemAttributes(QTreeWidgetItem *const item, const StorageBase *const devbase); QStringList getDeviceItemData(const StorageDevice *const dev); QStringList getPartitionItemData(const StoragePartition *const part); void restoreCurrentItem(const QString current, const QString currentParent); void setupContextMenu(); void setViewConfig(); public: DeviceTree(DeviceSizeChart *const chart, DevicePropertiesStack *const stack, QWidget *parent = nullptr); void loadData(QList devices); private slots: void popupContextMenu(QPoint point); signals: void deviceMenuRequested(QTreeWidgetItem *); }; #endif kvpm-0.9.10/kvpm/maxfs.h0000644000175000017500000000114412770324126015252 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef MAXFS_H #define MAXFS_H class LogVol; class StoragePartition; class StorageDevice; bool max_fs(LogVol *const logicalVolume); bool max_fs(StoragePartition *const partition); bool max_fs(StorageDevice *const device); #endif kvpm-0.9.10/kvpm/devicemenu.cpp0000644000175000017500000000365412770324126016623 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "devicemenu.h" #include #include #include #include #include "deviceactions.h" DeviceMenu::DeviceMenu(DeviceActions *const devacts, QWidget *parent) : QMenu(parent) { QMenu *const filesystem_menu = new QMenu(i18n("Filesystem operations"), this); QMenu *const vgextend_menu = new QMenu(i18n("Extend volume group"), this); vgextend_menu->setIcon(QIcon::fromTheme(QStringLiteral("add"))); addAction(devacts->action("tablecreate")); addSeparator(); addAction(devacts->action("partremove")); addAction(devacts->action("partadd")); addAction(devacts->action("partchange")); addAction(devacts->action("changeflags")); addAction(devacts->action("max_pv")); addSeparator(); addAction(devacts->action("vgcreate")); addAction(devacts->action("vgreduce")); addMenu(vgextend_menu); addSeparator(); addMenu(filesystem_menu); filesystem_menu->addAction(devacts->action("mount")); filesystem_menu->addAction(devacts->action("unmount")); filesystem_menu->addSeparator(); filesystem_menu->addAction(devacts->action("max_fs")); filesystem_menu->addAction(devacts->action("fsck")); filesystem_menu->addSeparator(); filesystem_menu->addAction(devacts->action("mkfs")); if (!devacts->actionGroups().isEmpty()) { QActionGroup *vgextend_actions = devacts->actionGroups()[0]; for (auto action : vgextend_actions->actions()) { vgextend_menu->addAction(action); } } } kvpm-0.9.10/kvpm/fsreduce.cpp0000644000175000017500000001227012770324126016271 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "fsreduce.h" #include #include #include #include "executablefinder.h" #include "fsblocksize.h" #include "fsck.h" #include "processprogress.h" bool fs_can_reduce(const QString fs) { const QString executable = "resize2fs"; if (fs == "ext2" || fs == "ext3" || fs == "ext4") { if (ExecutableFinder::getPath(executable).isEmpty()) { KMessageBox::error(nullptr, i18n("Executable: '%1' not found, this filesystem cannot be reduced", executable)); return false; } else { return true; } } else { return false; } } // Returns new fs size in bytes or 0 if no shrinking was done // Returns -1 if fs isn't one of ext2, ext3 or ext4 (not shrinkable) // Takes new_size in bytes. long long fs_reduce(const QString path, const long long new_size, const QString fs) { if (!fs_can_reduce(fs)) return -1; if (!fsck(path)) return 0; const long block_size = get_fs_block_size(path); if (block_size <= 0) { KMessageBox::error(nullptr, i18n("Shrink failed: could not determine filesystem block size")); return 0 ; } QStringList args = QStringList() << "resize2fs" << path << QString("%1K").arg(new_size / 1024); ProcessProgress fs_shrink(args); QStringList output = fs_shrink.programOutputAll(); QStringList success_stringlist = output.filter("is now"); // it worked QStringList nothing_stringlist = output.filter("is already"); // already a reduced fs, nothing to do! QStringList nospace_stringlist; nospace_stringlist << output.filter("space left"); // fs won't shrink that much -- old message nospace_stringlist << output.filter("than minimum"); // fs won't shrink that much -- new message QString size_string; if (success_stringlist.size() > 0) { // Try to shrink the desired amount size_string = success_stringlist[0]; size_string = size_string.remove(0, size_string.indexOf("now") + 3); size_string.truncate(size_string.indexOf("blocks")); if (size_string.indexOf("(") > -1) size_string.truncate(size_string.indexOf("(")); size_string = size_string.simplified(); return size_string.toLongLong() * block_size; } else if (nothing_stringlist.size() > 0) { size_string = nothing_stringlist[0]; size_string = size_string.remove(0, size_string.indexOf("already") + 7); size_string.truncate(size_string.indexOf("blocks")); if (size_string.indexOf("(") > -1) size_string.truncate(size_string.indexOf("(")); size_string = size_string.simplified(); return size_string.toLongLong() * block_size; } else if (nospace_stringlist.size() > 0) { // Couldn't shrink that much but try again with -M KMessageBox::information(nullptr, i18n("Now trying to shrink the filesystem to its minimum size")); args.clear(); success_stringlist.clear(); args << "resize2fs" << "-M" << path; ProcessProgress fs_shrink(args); output = fs_shrink.programOutput(); success_stringlist = output.filter("is now"); if (success_stringlist.size() > 0) { size_string = success_stringlist[0]; size_string = size_string.remove(0, size_string.indexOf("now") + 3); size_string.truncate(size_string.indexOf("blocks")); if (size_string.indexOf("(") > -1) size_string.truncate(size_string.indexOf("(")); size_string = size_string.simplified(); return size_string.toLongLong() * block_size; } } // Give up and do nothing return 0; } // Returns estimated minimum size of filesystem after shrinking, in bytes // Returns 0 on failure long long get_min_fs_size(const QString path, const QString fs) { long long size = 0; if (fs_can_reduce(fs)) { const long block_size = get_fs_block_size(path); if (block_size) { // if blocksize failed skip this part const QStringList args = QStringList() << "resize2fs" << "-P" << path; ProcessProgress fs_scan(args); const QStringList output = fs_scan.programOutput(); if (output.size() > 0 && fs_scan.exitCode() == 0) { QString size_string = output[0]; if (size_string.contains("Estimated", Qt::CaseInsensitive)) { size_string = size_string.remove(0, size_string.indexOf(":") + 1); size_string = size_string.simplified(); size = size_string.toLongLong() * block_size; } } } } return size; } kvpm-0.9.10/kvpm/executablefinder.h0000644000175000017500000000226412770324126017451 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2011, 2012 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef EXECUTABLEFINDER_H #define EXECUTABLEFINDER_H #include #include #include class ExecutableFinder : public QObject { Q_OBJECT QStringList m_default_search_paths; QStringList m_keys; // Names of the executables we are looking for QStringList m_not_found; // The ones we didn't find static QMap m_path_map; public: ExecutableFinder(QObject *parent = 0); static QString getPath(QString name); void reload(); // rescan the system for needed executables void reload(QStringList search); // As above using the stringlist for the search paths QStringList getAllPaths(); QStringList getAllNames(); QStringList getNotFound(); }; #endif kvpm-0.9.10/kvpm/vgremovemissing.cpp0000644000175000017500000000433512770324126017720 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgremovemissing.h" #include #include #include #include #include #include #include #include "processprogress.h" #include "volgroup.h" VGRemoveMissingDialog::VGRemoveMissingDialog(VolGroup *const group, QWidget *parent) : KvpmDialog(parent), m_vg(group) { setCaption(i18n("Remove missing physical volumes")); QHBoxLayout *const warning_layout = new QHBoxLayout(); QVBoxLayout *const layout = new QVBoxLayout(); QLabel *const icon_label = new QLabel(); icon_label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(64, 64)); warning_layout->addWidget(icon_label); warning_layout->addLayout(layout); QWidget *dialog_body = new QWidget(this); setMainWidget(dialog_body); dialog_body->setLayout(warning_layout); QLabel *message = new QLabel(i18n("Removing missing physical volumes may result in data loss. Use with extreme care.")); message->setWordWrap(true); layout->addWidget(message); layout->addSpacing(10); QGroupBox *radio_box = new QGroupBox(); QVBoxLayout *radio_box_layout = new QVBoxLayout(); radio_box->setLayout(radio_box_layout); layout->addWidget(radio_box); m_empty_button = new QRadioButton("Remove only empty physical volumes"); m_all_button = new QRadioButton("Remove all missing physical volumes"); m_empty_button->setChecked(true); radio_box_layout->addWidget(m_empty_button); radio_box_layout->addWidget(m_all_button); } void VGRemoveMissingDialog::commit() { hide(); QStringList args = QStringList() << "vgreduce" << "--removemissing"; if (m_all_button->isChecked()) args << "--force"; args << m_vg->getName(); ProcessProgress remove_missing(args); } kvpm-0.9.10/kvpm/dualselectorbox.cpp0000644000175000017500000001155612770324126017676 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include #include "dualselectorbox.h" #include "sizeselectorbox.h" DualSelectorBox::DualSelectorBox(const long long sectorSize, const long long totalSpace, QWidget *parent) : QWidget(parent), m_space(totalSpace) { QVBoxLayout *const layout = new QVBoxLayout(); layout->setMargin(0); const long ONE_MIB = 0x100000 / sectorSize; m_size_selector = new SizeSelectorBox(sectorSize, 2 * ONE_MIB, m_space, m_space, false, false, true); m_offset_selector = new SizeSelectorBox(sectorSize, 0, m_space - ONE_MIB, 0, false, true); layout->addWidget(m_size_selector); layout->addWidget(m_offset_selector); setLayout(layout); connect(m_size_selector, SIGNAL(stateChanged()), this , SLOT(sizeChanged())); connect(m_offset_selector, SIGNAL(stateChanged()), this , SLOT(offsetChanged())); } DualSelectorBox::DualSelectorBox(const long long sectorSize, const long long totalSpace, const long long minSize, const long long maxSize, const long long initialSize, const long long minOffset, const long long maxOffset, const long long initialOffset, QWidget *parent) : QWidget(parent), m_space(totalSpace) { QVBoxLayout *const layout = new QVBoxLayout(); layout->setMargin(0); m_size_selector = new SizeSelectorBox(sectorSize, minSize, maxSize, initialSize, false, false, true); if (minSize == maxSize) m_offset_selector = new SizeSelectorBox(sectorSize, minOffset, maxOffset, initialOffset, false, true, false, false); else m_offset_selector = new SizeSelectorBox(sectorSize, minOffset, maxOffset, initialOffset, false, true, false, true); layout->addWidget(m_size_selector); layout->addWidget(m_offset_selector); setLayout(layout); connect(m_size_selector, SIGNAL(stateChanged()), this , SLOT(sizeChanged())); connect(m_offset_selector, SIGNAL(stateChanged()), this , SLOT(offsetChanged())); } void DualSelectorBox::sizeChanged() { const long long max = m_space; const long long current_size = m_size_selector->getNewSize(); const long long current_offset = m_offset_selector->getNewSize(); if (m_size_selector->isValid()) { if (!m_offset_selector->isValid()) m_offset_selector->setNewSize(m_offset_selector->getNewSize()); // reset to last valid value if (!m_offset_selector->isLocked()) { if (m_size_selector->isLocked()) m_offset_selector->setConstrainedMax(max - current_size); else m_offset_selector->setConstrainedMax(max - m_size_selector->getMinimumSize()); if (m_offset_selector->getNewSize() > (max - current_size)) if (!m_offset_selector->setNewSize(max - current_size)) m_size_selector->setNewSize(max - current_offset); } else { m_size_selector->setConstrainedMax(max - current_offset); } } emit changed(); } void DualSelectorBox::offsetChanged() { const long long max = m_space; const long long current_size = m_size_selector->getNewSize(); const long long current_offset = m_offset_selector->getNewSize(); if (m_offset_selector->isValid()) { if (!m_size_selector->isValid()) m_size_selector->setNewSize(m_size_selector->getNewSize()); // poke it to make it valid if (!m_size_selector->isLocked()) { m_size_selector->setConstrainedMax(max); if (m_offset_selector->isLocked()) m_size_selector->setConstrainedMax(max - current_offset); else m_size_selector->setConstrainedMax(max - m_offset_selector->getMinimumSize()); if (m_size_selector->getNewSize() > (max - current_offset)) if (!m_size_selector->setNewSize(max - current_offset)) m_offset_selector->setNewSize(max - current_size); } else { m_offset_selector->setConstrainedMax(max - current_size); } } emit changed(); } void DualSelectorBox::resetSelectors() { m_size_selector->resetToInitial(); m_offset_selector->resetToInitial(); } long long DualSelectorBox::getNewSize() { return m_size_selector->getNewSize(); } long long DualSelectorBox::getNewOffset() { return m_offset_selector->getNewSize(); } bool DualSelectorBox::isValid() { return (m_size_selector->isValid() && m_offset_selector->isValid()); } kvpm-0.9.10/kvpm/vgimport.h0000644000175000017500000000075212770324126016007 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGIMPORT_H #define VGIMPORT_H class VolGroup; bool export_vg(VolGroup *const volumeGroup); #endif kvpm-0.9.10/kvpm/vgreduce.cpp0000644000175000017500000002210312770324126016271 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2012, 2013, 2014 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgreduce.h" #include "masterlist.h" #include "misc.h" #include "physvol.h" #include "pvgroupbox.h" #include "volgroup.h" #include #include #include #include #include #include #include // Remove one selected pv from group VGReduceDialog::VGReduceDialog(PhysVol *const pv, QWidget *parent) : KvpmDialog(parent), m_pv(pv), m_vg(pv->getVg()) { setCaption(i18n("Remove Physical Volume")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout; dialog_body->setLayout(layout); QLabel *const label = new QLabel(i18n("Remove physical volume: %1?", m_pv->getMapperName())); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); layout->addSpacing(5); QList > pv_space_list = getPvSpaceList(); if (pv->getSize() != pv->getRemaining()) { preventExec(); KMessageBox::sorry(nullptr, i18n("This physical volume is still in use and cannot be removed")); } else if (pv_space_list.size() == 1 && !hasUnremovablePv()) { preventExec(); KMessageBox::sorry(nullptr, i18n("There is only one physical volume in this group, try deleting the group instead")); } else if (!hasMda(QStringList( m_pv->getMapperName() ))) { preventExec(); KMessageBox::sorry(nullptr, i18n("Physical volume \'%1\' " "contains the only usable metadata area for this volume group " "and cannot be removed.", m_pv->getMapperName())); } setButtons(KDialog::Yes | KDialog::No); } // Remove one or more pvs from selected group VGReduceDialog::VGReduceDialog(VolGroup *const group, QWidget *parent) : KvpmDialog(parent), m_vg(group) { setCaption(i18n("Reduce Volume Group")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout; const QString vg_name = m_vg->getName(); QList > pv_space_list = getPvSpaceList(); if (pv_space_list.isEmpty()){ preventExec(); KMessageBox::sorry(nullptr, i18n("There are no physical volumes that can be removed")); } else if (pv_space_list.size() == 1 && !hasUnremovablePv()) { preventExec(); KMessageBox::sorry(nullptr, i18n("There is only one physical volume in this group, try deleting the group instead.")); } else if (pv_space_list.size() == 1 && !hasMda(QStringList(pv_space_list[0]->pv->getMapperName()))) { preventExec(); KMessageBox::sorry(nullptr, i18n("The only physical volume with no logical volumes on it is \'%1.\' " "It contains the only usable metadata area for this volume group " "and cannot be removed.", pv_space_list[0]->pv->getMapperName())); } else { QLabel *label; label = new QLabel(i18n("Reduce volume group: %1", m_vg->getName())); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); layout->addSpacing(10); if (pv_space_list.size() == 1) { label = new QLabel(i18n("This is the only physical volume that can be removed")); } else if (hasUnremovablePv()) { label = new QLabel(i18n("Select physical volumes to remove them from the volume group")); } else { label = new QLabel(i18n("Select physical volumes, excluding one, to " "remove them from the volume group")); } label->setWordWrap(true); QHBoxLayout *const label_layout = new QHBoxLayout; QWidget *const label_widget = new QWidget; label_layout->addWidget(label); label_widget->setLayout(label_layout); layout->addWidget(label_widget); m_pv_checkbox = new PvGroupBox(pv_space_list, NO_POLICY, NO_POLICY); m_pv_checkbox->setTitle(i18n("Unused physical volumes")); layout->addWidget(m_pv_checkbox); m_error_stack = createErrorWidget(); layout->addWidget(m_error_stack); m_error_stack->setCurrentIndex(0); connect(m_pv_checkbox, SIGNAL(stateChanged()), this, SLOT(resetOkButton())); m_pv_checkbox->selectNone(); } dialog_body->setLayout(layout); } void VGReduceDialog::commit() { const QByteArray vg_name = m_vg->getName().toLocal8Bit(); vg_t vg_dm = nullptr; lvm_t lvm = MasterList::getLvm(); QStringList pv_list; if (m_pv) pv_list << m_pv->getMapperName(); else if (m_pv_checkbox) pv_list << m_pv_checkbox->getNames(); // pvs to remove by name if ((vg_dm = lvm_vg_open(lvm, vg_name.data(), "w", 0))) { for (auto name : pv_list) { const QByteArray name_ba = name.toLocal8Bit(); if (lvm_vg_reduce(vg_dm, name_ba.data())) KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); } if (lvm_vg_write(vg_dm)) KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); lvm_vg_close(vg_dm); } else { KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); } return; } void VGReduceDialog::resetOkButton() { const QStringList names = m_pv_checkbox->getNames(); const int boxes_checked = names.size(); const int boxes_count = m_pv_checkbox->getAllNames().size(); if ((boxes_checked > 0) && (hasUnremovablePv() || (boxes_checked < boxes_count))) { if (hasMda(names)) { enableButtonOk(true); m_error_stack->setCurrentIndex(0); } else { enableButtonOk(false); m_error_stack->setCurrentIndex(1); // no remaining MDA } } else { if (boxes_checked > 0) { enableButtonOk(false); m_error_stack->setCurrentIndex(2); // no remaining pv } else { enableButtonOk(false); m_error_stack->setCurrentIndex(0); // nothing selected } } } // Checks to see if at least one pv not being removed has // a metadata area on it. A vg must always have at least one. bool VGReduceDialog::hasMda(const QStringList remove) { QStringList mda_names; for (auto pv : m_vg->getPhysicalVolumes()) { if (pv->getMdaUsed()) mda_names << pv->getMapperName(); } for (int x = mda_names.size() - 1; x >= 0 ; --x) { for (auto name : remove) { if (name == mda_names[x]) { mda_names.removeAt(x); break; } } } return !mda_names.isEmpty(); } QStackedWidget *VGReduceDialog::createErrorWidget() { QStackedWidget *const stack = new QStackedWidget(); stack->addWidget(new QWidget); QLabel *icon_label; QLabel *error_label; QWidget *const error_widget1 = new QWidget(); stack->addWidget(error_widget1); QHBoxLayout *const layout1 = new QHBoxLayout(); icon_label = new QLabel(""); icon_label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(32, 32)); error_label = new QLabel(i18n("Cannot remove all the physical volumes with usable metadata areas")); error_label->setWordWrap(true); layout1->addWidget(icon_label); layout1->addWidget(error_label); layout1->addStretch(); error_widget1->setLayout(layout1); QWidget *const error_widget2 = new QWidget(); stack->addWidget(error_widget2); QHBoxLayout *const layout2 = new QHBoxLayout(); icon_label = new QLabel(""); icon_label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(32, 32)); error_label = new QLabel(i18n("A volume group must always have at least " "one physical volume in it")); error_label->setWordWrap(true); layout2->addWidget(icon_label); layout2->addWidget(error_label); layout2->addStretch(); error_widget2->setLayout(layout2); return stack; } QList > VGReduceDialog::getPvSpaceList() { QList > list; for (auto pv : m_vg->getPhysicalVolumes()) { if (pv->getSize() == pv->getRemaining()) list << QSharedPointer(new PvSpace(pv, pv->getSize(), pv->getSize())); } return list; } bool VGReduceDialog::hasUnremovablePv() { bool unremovable = false; for (auto pv : m_vg->getPhysicalVolumes()) { if (pv->getSize() != pv->getRemaining()) { // only unused pvs can be removed unremovable = true; break; } } return unremovable; } kvpm-0.9.10/kvpm/maintabwidget.cpp0000644000175000017500000000434512770324126017314 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011, 2012, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "maintabwidget.h" #include #include #include #include #include "devicetab.h" #include "volumegrouptab.h" MainTabWidget::MainTabWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *const layout = new QVBoxLayout(); m_tab_widget = new QTabWidget(); m_tab_widget->setMovable(false); m_tab_widget->setTabsClosable(false); layout->addWidget(m_tab_widget); m_unmunged_text.clear(); setLayout(layout); connect(m_tab_widget, SIGNAL(currentChanged(int)), this, SIGNAL(currentIndexChanged(int))); } QString MainTabWidget::getUnmungedText(const int index) { return m_unmunged_text[index]; } void MainTabWidget::appendVolumeGroupTab(VolumeGroupTab *const page, const QIcon &icon, const QString &label) { m_tab_widget->insertTab(m_tab_widget->count(), static_cast(page), icon, i18n("Group: %1", label)); m_unmunged_text.append(label); m_vg_tabs.append(page); } void MainTabWidget::appendDeviceTab(DeviceTab *const page, const QString &label) { m_tab_widget->insertTab(m_tab_widget->count(), static_cast(page), label); m_unmunged_text.append(label); } void MainTabWidget::deleteTab(const int index) { m_tab_widget->widget(index)->deleteLater(); m_tab_widget->removeTab(index); m_unmunged_text.removeAt(index); m_vg_tabs.removeAt(index - 1); } QWidget *MainTabWidget::getWidget(const int index) { return m_tab_widget->widget(index); } int MainTabWidget::getCount() { return m_tab_widget->count(); } int MainTabWidget::getCurrentIndex() { return m_tab_widget->currentIndex(); } VolumeGroupTab *MainTabWidget::getVolumeGroupTab(const int index) { return m_vg_tabs[index]; } void MainTabWidget::setIcon(const int index, const QIcon &icon) { m_tab_widget->setTabIcon(index, icon); } kvpm-0.9.10/kvpm/pvpropertiesstack.h0000644000175000017500000000205012770324126017721 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVPROPERTIESSTACK_H #define PVPROPERTIESSTACK_H #include #include class QLabel; class QStackedWidget; class QTreeWidgetItem; class QScrollArea; class PhysVol; class VolGroup; class PVPropertiesStack : public QFrame { Q_OBJECT QStackedWidget *m_stack_widget; bool m_is_pv; VolGroup *m_vg; QList m_pv_stack_list; QLabel *m_pv_label; // The name of the device QScrollArea *m_vscroll; public: explicit PVPropertiesStack(VolGroup *volumeGroup, QWidget *parent = 0); void loadData(); public slots: void changePVStackIndex(QTreeWidgetItem *item, QTreeWidgetItem*); }; #endif kvpm-0.9.10/kvpm/kvpmdialog.h0000644000175000017500000000225612770324126016276 0ustar benscottbenscott/* * * * Copyright (C) 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef KVPMDIALOG_H #define KVPMDIALOG_H #include /* Sometimes the constuctor of a dialog is called with arguments that make executing the dialog pointless. The run() function is a substitute for the exec() function. If the subclass calls preventExec() in its constructor then any subsequent call to run will exit without calling exec(). Otherwise exec() is called and the dialog behaves normally. */ class KvpmDialog : public KDialog { Q_OBJECT bool m_allow_run = true; protected: void preventExec(); // call this if the dialog should not be exectued bool willExec(); // True if preventRun was never called protected slots: virtual void commit() = 0; public: explicit KvpmDialog(QWidget *parent = nullptr); virtual ~KvpmDialog(); int run(); }; #endif kvpm-0.9.10/kvpm/mounttables.h0000644000175000017500000000250212770324126016470 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef MOUNTTABLES_H #define MOUNTTABLES_H #include class LogVol; class StoragePartition; class MountEntry; using MountPtr = QSharedPointer; using MountList = QList; class MountTables { MountList m_mount_list; MountList m_fstab_list; QString getFstabMountPoint(const QString name, const QString label, const QString uuid); public: MountTables(); ~MountTables(); void loadData(); MountList getMtabEntries(const int major, const int minor); QString getFstabMountPoint(LogVol *const lv); QString getFstabMountPoint(StoragePartition *const partition); static bool addEntry(const QString device, const QString mountPoint, const QString type, const QString options, const int dumpFreq, const int pass); static bool renameEntries(const QString oldName, const QString newName); static bool removeEntry(const QString mountPoint); }; #endif kvpm-0.9.10/kvpm/allocationpolicy.h0000644000175000017500000000254612770324126017510 0ustar benscottbenscott/* * * * Copyright (C) 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef ALLOCATIONPOLICY_H #define ALLOCATIONPOLICY_H #include class QString; class QComboBox; typedef enum { NO_POLICY = 0, NORMAL = 1, CONTIGUOUS = 2, CLING = 3, ANYWHERE = 4, INHERIT_NORMAL = 5, // Don't change order, INHERIT_* must be after other policies INHERIT_CONTIGUOUS = 6, INHERIT_CLING = 7, INHERIT_ANYWHERE = 8, } AllocationPolicy; QString policyToString(const AllocationPolicy policy); QString policyToLocalString(const AllocationPolicy policy); class PolicyComboBox: public QWidget { Q_OBJECT private: QComboBox *m_combo; AllocationPolicy m_vg_policy; public: explicit PolicyComboBox(const AllocationPolicy policy, const AllocationPolicy vgpolicy = NO_POLICY, QWidget *parent = NULL); AllocationPolicy getEffectivePolicy(); AllocationPolicy getPolicy(); private slots: void emitNewPolicy(); signals: void policyChanged(AllocationPolicy policy); }; #endif kvpm-0.9.10/kvpm/lvremove.cpp0000644000175000017500000001247012770324126016332 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvremove.h" #include #include #include #include #include #include #include "logvol.h" #include "processprogress.h" LVRemoveDialog::LVRemoveDialog(const LogVol *const lv, QWidget *parent) : KvpmDialog(parent), m_lv(lv) { setButtons(KDialog::Yes | KDialog::No); setDefaultButton(KDialog::No); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); m_name = m_lv->getName(); QHBoxLayout *const layout = new QHBoxLayout(); QVBoxLayout *const right_layout = new QVBoxLayout(); QLabel *const icon_label = new QLabel(); icon_label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(64, 64)); layout->addWidget(icon_label); layout->addLayout(right_layout); QLabel *label; if(m_lv->isThinPool()) { setCaption(i18n("Delete Thin Pool")); label = new QLabel("Confirm Thin Pool Deletion"); } else { setCaption(i18n("Delete Volume")); label = new QLabel("Confirm Volume Deletion"); } label->setAlignment(Qt::AlignCenter); right_layout->addWidget(label); right_layout->addSpacing(20); QStringList children(getDependentChildren(m_lv)); children.sort(); children.removeDuplicates(); if (children.isEmpty()) { if(m_lv->isThinPool()) { right_layout->addWidget(new QLabel(i18n("Delete the thin pool named: %1?", "" + m_name + ""))); } else { right_layout->addWidget(new QLabel(i18n("Delete the volume named: %1?", "" + m_name + ""))); right_layout->addWidget(new QLabel(i18n("Any data on it will be lost."))); } } else { if(m_lv->isThinPool()) right_layout->addWidget(new QLabel(i18n("The thin pool: %1 has dependent volumes.", m_name))); else right_layout->addWidget(new QLabel(i18n("The volume: %1 has dependent volumes.", m_name))); right_layout->addWidget(new QLabel(i18n("The following volumes will all be deleted:"))); right_layout->addSpacing(10); QWidget *const list = new QWidget(); QVBoxLayout *const list_layout = new QVBoxLayout(); list->setLayout(list_layout); label = new QLabel("" + m_name + ""); label->setAlignment(Qt::AlignLeft); list_layout->addWidget(label); list_layout->addSpacing(10); for (int x = 0; x < children.size(); x++) { label = new QLabel("" + children[x] + ""); label->setAlignment(Qt::AlignLeft); list_layout->addWidget(label); } QScrollArea *const scroll = new QScrollArea(); scroll->setWidget(list); right_layout->addWidget(scroll); right_layout->addSpacing(10); right_layout->addWidget(new QLabel(i18n("Are you certain you want to delete these volumes?"))); right_layout->addWidget(new QLabel(i18n("Any data on them will be lost."))); } dialog_body->setLayout(layout); } QStringList LVRemoveDialog::getDependentChildren(const LogVol *const lv) { QStringList children; bool bailout = false; if (lv->isCowOrigin()) { QListIterator snap_itr(lv->getSnapshots()); LogVol *snap; while (snap_itr.hasNext()) { snap = snap_itr.next(); if (snap->isMounted()) bailout = true; children.append(snap->getName()); } } else if (lv->isThinPool()){ QListIterator thin_itr(lv->getThinVolumes()); LogVol *thin; while (thin_itr.hasNext()) { thin = thin_itr.next(); if (thin->isMounted()) bailout = true; children.append(thin->getName()); if (thin->isCowOrigin()) children.append(getDependentChildren(thin)); } } if (bailout) { hide(); preventExec(); if (lv->isThinPool()) KMessageBox::sorry(this, i18n("A volume of this thin pool is busy or mounted. It can not be deleted.")); else KMessageBox::sorry(this, i18n("A snapshot of this origin is busy or mounted. It can not be deleted.")); } return children; } void LVRemoveDialog::commit() { hide(); const QString full_name = m_lv->getFullName().remove('[').remove(']'); QStringList args; if (m_lv->isActive() && !m_lv->isCowSnap()) { args << "lvchange" << "-an" << full_name; ProcessProgress deactivate(args); } args.clear(); args << "lvremove" << "--force" << full_name; ProcessProgress remove(args); return; } kvpm-0.9.10/kvpm/fsextend.h0000644000175000017500000000130212770324126015750 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2014 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef FSEXTEND_H #define FSEXTEND_H #include bool fs_extend(const QString dev, const QString fs, const QStringList mps, const bool isLV = false); bool fs_can_extend(const QString fs, const bool mounted); long long fs_max_extend(const QString dev, const QString fs, const bool mounted); #endif kvpm-0.9.10/kvpm/progressbox.h0000644000175000017500000000146712770324126016521 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PROGRESSBOX_H #define PROGRESSBOX_H #include #include #include #include class ProgressBox : public QFrame { QLabel *m_message; QProgressBar *m_progressbar; public: explicit ProgressBox(QWidget *parent = NULL); void reset(); void setText(const QString text); void setRange(const int start, const int end); void setValue(const int value); }; #endif kvpm-0.9.10/kvpm/storagebase.h0000644000175000017500000000425412770324126016440 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2014 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef STORAGEBASE_H #define STORAGEBASE_H #include #include #include class PhysVol; class StorageBase { long long m_sector_size; // Size in bytes QString m_name; bool m_is_writable; bool m_is_busy; bool m_is_pv; bool m_is_dmraid; bool m_is_dmraid_block; bool m_is_mdraid; bool m_is_mdraid_block; PhysVol *m_pv; int m_major; // block dev numbers int m_minor; void commonConstruction(const QList &pvList); public: StorageBase(PedDevice *const device, const QList &pvList, const QStringList dmblock, const QStringList dmraid, const QStringList mdblock, const QStringList mdraid); StorageBase(PedPartition *const part, const QList &pvList, const QStringList mdblock); virtual ~StorageBase() {} virtual long long getSize() const = 0; virtual QString getName() const { return m_name; } // the full device path PhysVol *getPhysicalVolume() const { return m_pv; } long long getSectorSize() const { return m_sector_size; } // Size in bytes int getMajorNumber() const { return m_major; } int getMinorNumber() const { return m_minor; } bool isWritable() const { return m_is_writable; } bool isBusy() const { return m_is_busy; } bool isPhysicalVolume() const { return m_is_pv; } bool isDmRaid() const { return m_is_dmraid; } // dmraid device ie: /dev/mapper/foo bool isDmBlock() const { return m_is_dmraid_block; } // real block device under dmraid bool isMdRaid() const { return m_is_mdraid; } // mdraid device ie: /dev/mdfoo bool isMdBlock() const { return m_is_mdraid_block; } // real block device under mdraid }; #endif kvpm-0.9.10/kvpm/lvremove.h0000644000175000017500000000145712770324126016002 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVREMOVE_H #define LVREMOVE_H #include #include "kvpmdialog.h" class LogVol; class LVRemoveDialog : public KvpmDialog { Q_OBJECT QString m_name; const LogVol *const m_lv; QStringList getDependentChildren(const LogVol *const lv); public: explicit LVRemoveDialog(const LogVol *const lv, QWidget *parent = nullptr); private slots: void commit(); }; #endif kvpm-0.9.10/kvpm/tablecreate.cpp0000644000175000017500000000776412770324126016760 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "tablecreate.h" #include #include #include #include #include #include #include #include // Creates or deletes a partition table "disk label" on a device. bool create_table(const QString devicePath) { const QString warning_message = i18n("Writing a new partition table to this device, " "or removing the old one, will cause " "any existing data on it to be permanently lost"); if (KMessageBox::warningContinueCancel(nullptr, warning_message, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return false; } else { TableCreateDialog dialog(devicePath); dialog.exec(); if (dialog.result() == QDialog::Accepted) return true; else return false; } } TableCreateDialog::TableCreateDialog(const QString devicePath, QWidget *parent) : KDialog(parent), m_device_path(devicePath) { setCaption(i18n("Create Partition Table")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); layout->addWidget(new QLabel(i18n("Create partition table on:"))); QLabel *const device_label = new QLabel("" + m_device_path + ""); device_label->setAlignment(Qt::AlignHCenter); layout->addWidget(device_label); QGroupBox *const radio_box = new QGroupBox(i18n("Table Types")); QVBoxLayout *const radio_box_layout = new QVBoxLayout(); radio_box->setLayout(radio_box_layout); layout->addWidget(radio_box); m_msdos_button = new QRadioButton(i18n("MS-DOS")); m_gpt_button = new QRadioButton(i18n("GPT")); m_destroy_button = new QRadioButton(i18n("Remove table")); m_msdos_button->setChecked(true); radio_box_layout->addWidget(m_msdos_button); radio_box_layout->addWidget(m_gpt_button); radio_box_layout->addWidget(m_destroy_button); connect(this, SIGNAL(okClicked()), this, SLOT(commitTable())); } void TableCreateDialog::commitTable() { QByteArray path = m_device_path.toLocal8Bit(); PedDevice *const ped_device = ped_device_get(path.data()); PedDiskType *ped_disk_type = nullptr; PedDisk *ped_disk = nullptr; if (m_msdos_button->isChecked()) { ped_disk_type = ped_disk_type_get("msdos"); ped_disk = ped_disk_new_fresh(ped_device, ped_disk_type); ped_disk_commit(ped_disk); } else if (m_gpt_button->isChecked()) { ped_disk_type = ped_disk_type_get("gpt"); ped_disk = ped_disk_new_fresh(ped_device, ped_disk_type); ped_disk_commit(ped_disk); } else { ped_disk_clobber(ped_device); // This isn't enough for lvm ped_device_open(ped_device); char *const buff = static_cast(malloc(2 * ped_device->sector_size)); for (int x = 0; x < 2 * ped_device->sector_size; x++) buff[x] = 0; if (!ped_device_write(ped_device, buff, 0, 2)) // clobber first 2 sectors KMessageBox::error(0, "Destroying table failed: could not write to device"); ped_device_close(ped_device); free(buff); } } kvpm-0.9.10/kvpm/storagedevice.h0000644000175000017500000000336512770324126016767 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef STORAGEDEVICE_H #define STORAGEDEVICE_H #include #include #include "storagebase.h" class MountTables; class PhysVol; class StoragePartition; class StorageDevice : public StorageBase { long long m_device_size; // Size in bytes QString m_disk_label; QString m_hardware; long long m_physical_sector_size; int m_freespace_count; QList m_storage_partitions; public: StorageDevice(PedDevice *const pedDevice, const QList pvList, MountTables *const tables, const QStringList dmblock, const QStringList dmraid, const QStringList mdblock, const QStringList mdraid); ~StorageDevice(); QString getDiskLabel() const { return m_disk_label; } QString getHardware() const { return m_hardware; } QList getStoragePartitions() const { return m_storage_partitions; } int getPartitionCount() const { return m_storage_partitions.size(); } int getRealPartitionCount() const { return m_storage_partitions.size() - m_freespace_count; } long long getSize() const { return m_device_size; } // Size in bytes long long getPhysicalSectorSize() const { return m_physical_sector_size; } }; #endif kvpm-0.9.10/kvpm/removemirror.h0000644000175000017500000000153212770324126016665 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVREMOVEMIRROR_H #define LVREMOVEMIRROR_H #include "kvpmdialog.h" class LogVol; class NoMungeCheck; class VolGroup; class RemoveMirrorDialog : public KvpmDialog { Q_OBJECT LogVol *m_lv = nullptr; const VolGroup *m_vg = nullptr; QList m_leg_checks; public: explicit RemoveMirrorDialog(LogVol *mirror, QWidget *parent = nullptr); private slots: void validateCheckStates(); void commit(); }; #endif kvpm-0.9.10/kvpm/lvrename.cpp0000644000175000017500000000557612770324126016315 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvrename.h" #include #include #include #include #include #include #include "logvol.h" #include "mounttables.h" #include "volgroup.h" #include "processprogress.h" LVRenameDialog::LVRenameDialog(LogVol *const volume, QWidget *parent) : KvpmDialog(parent), m_lv(volume) { setCaption(i18n("Rename Logical Volume")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); m_vg_name = m_lv->getVg()->getName(); m_old_name = m_lv->getName(); QLabel *label; if (m_lv->isThinPool()) label = new QLabel(i18n("Rename Thin Pool")); else label = new QLabel(i18n("Rename Logical Volume")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); layout->addSpacing(10); label = new QLabel(i18n("Current name: %1", m_old_name)); layout->addWidget(label); QRegExp rx("[0-9a-zA-Z_\\.][-0-9a-zA-Z_\\.]*"); m_new_name = new QLineEdit(); m_name_validator = new QRegExpValidator(rx, m_new_name); m_new_name->setValidator(m_name_validator); QHBoxLayout *const name_layout = new QHBoxLayout(); label = new QLabel(i18n("New name: ")); name_layout->addWidget(label); name_layout->addWidget(m_new_name); layout->addLayout(name_layout); enableButtonOk(false); connect(m_new_name, SIGNAL(textChanged(QString)), this, SLOT(validateName(QString))); } /* The allowed characters in the name are letters, numbers, periods hyphens and underscores. Also the names ".", ".." and names starting with a hyphen are disallowed */ void LVRenameDialog::validateName(QString name) { int pos = 0; if (m_name_validator->validate(name, pos) == QValidator::Acceptable && name != "." && name != "..") { enableButtonOk(true); } else enableButtonOk(false); } QString LVRenameDialog::getNewMapperPath() { QString path = m_lv->getMapperPath(); path.truncate(path.lastIndexOf('/') + 1); return QString(path + m_new_name->text()); } void LVRenameDialog::commit() { QStringList args; args << "lvrename" << m_vg_name << m_old_name << m_new_name->text(); ProcessProgress rename(args); if (!rename.exitCode() && m_lv->isMounted()) MountTables::renameEntries(m_lv->getMapperPath(), getNewMapperPath()); } kvpm-0.9.10/kvpm/vgcreate.cpp0000644000175000017500000003115412770324126016273 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgcreate.h" #include "masterlist.h" #include "progressbox.h" #include "pvgroupbox.h" #include "storagebase.h" #include "storagedevice.h" #include "storagepartition.h" #include "topwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include VGCreateDialog::VGCreateDialog(QWidget *parent) : KvpmDialog(parent) { QList devices; devices = getUsablePvs(); if (devices.size() > 0) { if (continueWarning()) buildDialog(devices); else preventExec(); } else { preventExec(); KMessageBox::sorry(nullptr, i18n("No unused potential physical volumes found")); } } VGCreateDialog::VGCreateDialog(StorageBase *const device, QWidget *parent) : KvpmDialog(parent) { QList devices; devices.append(device); if (continueWarning()) buildDialog(devices); else preventExec(); } bool VGCreateDialog::continueWarning() { const QString warning = i18n("If a device or partition is added to a volume group, " "any data currently on that device or partition will be lost."); return (KMessageBox::warningContinueCancel(nullptr, warning, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) == KMessageBox::Continue); } void VGCreateDialog::extentSizeChanged() { limitExtentSize(m_extent_suffix->currentIndex()); long long new_extent_size = m_extent_size->currentText().toLongLong(); new_extent_size *= 1024; if (m_extent_suffix->currentIndex() > 0) new_extent_size *= 1024; if (m_extent_suffix->currentIndex() > 1) new_extent_size *= 1024; m_pv_checkbox->setExtentSize(new_extent_size); } void VGCreateDialog::limitExtentSize(int index) { int extent_index; if (index > 1) { // Gigabytes selected as suffix, more than 2Gib forbidden if (m_extent_size->currentIndex() > 2) m_extent_size->setCurrentIndex(0); m_extent_size->setMaxCount(2); } else { extent_index = m_extent_size->currentIndex(); m_extent_size->setMaxCount(10); m_extent_size->setInsertPolicy(QComboBox::InsertAtBottom); m_extent_size->insertItem(2, "4"); m_extent_size->insertItem(3, "8"); m_extent_size->insertItem(4, "16"); m_extent_size->insertItem(5, "32"); m_extent_size->insertItem(6, "64"); m_extent_size->insertItem(7, "128"); m_extent_size->insertItem(8, "256"); m_extent_size->insertItem(9, "512"); m_extent_size->setInsertPolicy(QComboBox::NoInsert); m_extent_size->setCurrentIndex(extent_index); } } void VGCreateDialog::commit() { lvm_t lvm = MasterList::getLvm(); vg_t vg_dm; uint32_t new_extent_size = m_extent_size->currentText().toULong(); const QStringList pv_names = m_pv_checkbox->getNames(); const QByteArray vg_name_array = m_vg_name->text().toLocal8Bit(); ProgressBox *const progress_box = TopWindow::getProgressBox(); QByteArray pv_name_qba; hide(); new_extent_size *= 1024; if (m_extent_suffix->currentIndex() > 0) new_extent_size *= 1024; if (m_extent_suffix->currentIndex() > 1) new_extent_size *= 1024; progress_box->setRange(0, pv_names.size()); progress_box->setText(i18n("Creating Group")); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if ((vg_dm = lvm_vg_create(lvm, vg_name_array.data()))) { if ((lvm_vg_set_extent_size(vg_dm, new_extent_size))) KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); for (int i = 0; i < pv_names.size(); ++i) { progress_box->setValue(i); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); QByteArray name = pv_names[i].toLocal8Bit(); pv_create_params_t params = lvm_pv_params_create(lvm, name.data()); lvm_property_value_t value; value.is_settable = 1; value.is_string = 0; value.is_integer = 1; value.is_valid = 1; value.is_signed = 0; value.value.integer = m_copies_combo->currentIndex() + 1; lvm_pv_params_set_property(params, "pvmetadatacopies", &value); if (m_size_edit->hasAcceptableInput()) { value.value.integer = 2 * m_size_edit->text().toInt(); lvm_pv_params_set_property(params, "pvmetadatasize", &value); } if (m_align_edit->hasAcceptableInput()) { value.value.integer = 2 * m_align_edit->text().toInt(); lvm_pv_params_set_property(params, "data_alignment", &value); } if (m_offset_edit->hasAcceptableInput()) { value.value.integer = 2 * m_offset_edit->text().toInt(); lvm_pv_params_set_property(params, "data_alignment_offset", &value); } lvm_pv_create_adv(params); if (lvm_vg_extend(vg_dm, name.data())) KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); } // ****To Do... None of the following are supported by liblvm2app yet**** // if(m_clustered->isChecked()) // if(m_auto_backup->isChecked()) if (lvm_vg_write(vg_dm)) KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); lvm_vg_close(vg_dm); progress_box->reset(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); return; } KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); progress_box->reset(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); return; } /* The allowed characters in the name are letters, numbers, periods hyphens and underscores. Also, the names ".", ".." and names starting with a hyphen are disallowed. Finally disable the OK button if no pvs are checked */ void VGCreateDialog::validateOK() { QString name = m_vg_name->text(); int pos = 0; long long space = m_pv_checkbox->getRemainingSpace(); enableButtonOk(false); if (m_validator->validate(name, pos) == QValidator::Acceptable && name != "." && name != "..") { if (space) enableButtonOk(true); } } void VGCreateDialog::buildDialog(const QList devices) { setCaption(i18n("Create Volume Group")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); QLabel *const title = new QLabel(i18n("Create volume group")); title->setAlignment(Qt::AlignCenter); layout->addSpacing(5); layout->addWidget(title); layout->addSpacing(10); QTabWidget *const tab_widget = new QTabWidget(this); layout->addWidget(tab_widget); tab_widget->addTab(buildGeneralTab(devices), "General"); tab_widget->addTab(buildAdvancedTab(), "Advanced"); } QWidget *VGCreateDialog::buildGeneralTab(QList devices) { QWidget *const tab = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); tab->setLayout(layout); QLabel *const name_label = new QLabel(i18n("Group name: ")); m_vg_name = new QLineEdit(); name_label->setBuddy(m_vg_name); QRegExp rx("[0-9a-zA-Z_\\.][-0-9a-zA-Z_\\.]*"); m_validator = new QRegExpValidator(rx, m_vg_name); m_vg_name->setValidator(m_validator); QHBoxLayout *const name_layout = new QHBoxLayout(); name_layout->addWidget(name_label); name_layout->addWidget(m_vg_name); QLabel *const extent_label = new QLabel(i18n("Physical extent size: ")); m_extent_size = new QComboBox(); extent_label->setBuddy(m_extent_size); m_extent_size->insertItem(0, "1"); m_extent_size->insertItem(1, "2"); m_extent_size->insertItem(2, "4"); m_extent_size->insertItem(3, "8"); m_extent_size->insertItem(4, "16"); m_extent_size->insertItem(5, "32"); m_extent_size->insertItem(6, "64"); m_extent_size->insertItem(7, "128"); m_extent_size->insertItem(8, "256"); m_extent_size->insertItem(9, "512"); m_extent_size->setInsertPolicy(QComboBox::NoInsert); m_extent_size->setCurrentIndex(2); m_extent_suffix = new QComboBox(); m_extent_suffix->insertItem(0, "KiB"); m_extent_suffix->insertItem(1, "MiB"); m_extent_suffix->insertItem(2, "GiB"); m_extent_suffix->setInsertPolicy(QComboBox::NoInsert); m_extent_suffix->setCurrentIndex(1); m_pv_checkbox = new PvGroupBox(devices, 0x400000); // 4 MiB default extent size layout->addWidget(m_pv_checkbox); connect(m_pv_checkbox, SIGNAL(stateChanged()), this, SLOT(validateOK())); QHBoxLayout *extent_layout = new QHBoxLayout(); extent_layout->addWidget(extent_label); extent_layout->addWidget(m_extent_size); extent_layout->addWidget(m_extent_suffix); extent_layout->addStretch(); m_clustered = new QCheckBox(i18n("Cluster Aware")); m_clustered->setEnabled(false); m_auto_backup = new QCheckBox(i18n("Automatic Backup")); m_auto_backup->setCheckState(Qt::Checked); m_auto_backup->setEnabled(false); layout->addLayout(name_layout); layout->addLayout(extent_layout); layout->addWidget(m_clustered); layout->addWidget(m_auto_backup); enableButtonOk(false); connect(m_vg_name, SIGNAL(textChanged(QString)), this, SLOT(validateOK())); connect(m_extent_size, SIGNAL(activated(int)), this, SLOT(extentSizeChanged())); connect(m_extent_suffix, SIGNAL(activated(int)), this, SLOT(extentSizeChanged())); return tab; } QWidget *VGCreateDialog::buildAdvancedTab() { QWidget *tab = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); tab->setLayout(layout); QHBoxLayout *const copies_layout = new QHBoxLayout(); QLabel *const copies_label = new QLabel("Metadata copies: "); copies_layout->addWidget(copies_label); m_copies_combo = new QComboBox(); m_copies_combo->addItem(i18n("1")); m_copies_combo->addItem(i18n("2")); m_copies_combo->setCurrentIndex(0); copies_layout->addWidget(m_copies_combo); copies_layout->addStretch(); QLabel *const unit_label = new QLabel(i18n("All values are in KiloBytes")); unit_label->setAlignment(Qt::AlignCenter); QHBoxLayout *const size_layout = new QHBoxLayout(); QLabel *const size_label = new QLabel(i18n("Metadata size:")); size_layout->addWidget(size_label); m_size_edit = new QLineEdit; QIntValidator *const size_validator = new QIntValidator(); size_validator->setBottom(0); m_size_edit->setValidator(size_validator); m_size_edit->setPlaceholderText(i18n("default")); size_layout->addWidget(m_size_edit); QHBoxLayout *const align_layout = new QHBoxLayout(); QLabel *align_label = new QLabel(i18n("Metadata align:")); align_layout->addWidget(align_label); m_align_edit = new QLineEdit; QIntValidator *const align_validator = new QIntValidator(); align_validator->setBottom(0); m_align_edit->setValidator(align_validator); m_align_edit->setPlaceholderText(i18n("default")); align_layout->addWidget(m_align_edit); QHBoxLayout *const offset_layout = new QHBoxLayout(); QLabel *offset_label = new QLabel(i18n("Metadata offset:")); offset_layout->addWidget(offset_label); m_offset_edit = new QLineEdit; QIntValidator *const offset_validator = new QIntValidator(); offset_validator->setBottom(0); m_offset_edit->setValidator(offset_validator); m_offset_edit->setPlaceholderText(i18n("default")); offset_layout->addWidget(m_offset_edit); layout->addLayout(copies_layout); layout->addWidget(unit_label); layout->addLayout(size_layout); layout->addLayout(align_layout); layout->addLayout(offset_layout); layout->addStretch(); return tab; } kvpm-0.9.10/kvpm/lvmconfig.cpp0000644000175000017500000001256212770324126016461 0ustar benscottbenscott/* * * * Copyright (C) 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvmconfig.h" #include "executablefinder.h" #include "processprogress.h" #include #include #include // These are static being variables initialized here long long LvmConfig::m_mirror_region_size = 0x80000; // 512KiB QString LvmConfig::m_mirror_segtype_default = QString("mirror"); bool LvmConfig::m_mirror_logs_require_separate_pvs = false; bool LvmConfig::m_thin_pool_metadata_require_separate_pvs = false; bool LvmConfig::m_maximise_cling = false; // This class holds the lvm2 system configuration settings. It // is meant to be initialized once, at startup, and then // globally readable with static members. LvmConfig::LvmConfig() { } LvmConfig::~LvmConfig() { } // runs 'lvm dumpconfig' and sets the static variables void LvmConfig::initialize() { QStringList output = getConfig(); auto line = output.constBegin(); while (line != output.constEnd()) { if (line->contains("allocation {")) { QStringList allocation; ++line; while ((line != output.constEnd()) && (!line->startsWith("}"))) { allocation << line->trimmed(); ++line; } setAllocation(allocation); } else if (line->contains("global {")) { QStringList global; ++line; while ((line != output.constEnd()) && (!line->startsWith("}"))) { global << line->trimmed(); ++line; } setGlobal(global); } else if (line->contains("activation {")) { QStringList activation; ++line; while ((line != output.constEnd()) && (!line->startsWith("}"))) { activation << line->trimmed(); ++line; } setActivation(activation); } else { ++line; } } } QStringList LvmConfig::getConfig() { QStringList output; const QString executable_path = ExecutableFinder::getPath("lvm"); const QStringList args = QStringList() << "dumpconfig"; if (!executable_path.isEmpty()) { QStringList environment = QProcess::systemEnvironment(); environment << "LVM_SUPPRESS_FD_WARNINGS=1"; KProcess process; process.setEnvironment(environment); process.setOutputChannelMode(KProcess::SeparateChannels); process.setReadChannel(QProcess::StandardOutput); process.setProgram(executable_path, args); process.start(); process.closeWriteChannel(); process.waitForFinished(); process.setReadChannel(QProcess::StandardOutput); while (process.canReadLine()) output << process.readLine(); if (process.exitCode() || (process.exitStatus() == QProcess::CrashExit)) { process.setReadChannel(QProcess::StandardError); QString errors; while (process.canReadLine()) errors.append(process.readLine()); if (process.exitStatus() != QProcess::CrashExit) KMessageBox::error(NULL, i18n("%1 produced this output: %2", process.program().takeFirst(), errors)); else KMessageBox::error(NULL, i18n("%1 crashed with this output: %2", process.program().takeFirst(), errors)); } } else { KMessageBox::error(NULL, i18n("Executable: '%1' not found", executable_path)); } return output; } void LvmConfig::setGlobal(const QStringList &variables) { for (auto line : variables) { if (line.contains("mirror_segtype_default")) { if (line.contains("\"raid1\"") ) m_mirror_segtype_default = "raid1"; else m_mirror_segtype_default = "mirror"; } } } void LvmConfig::setAllocation(const QStringList &variables) { for (auto line : variables) { if (line.contains("mirror_logs_require_separate_pvs")) m_mirror_logs_require_separate_pvs = line.contains("=1"); if (line.contains("thin_pool_metadata_require_separate_pvs")) m_thin_pool_metadata_require_separate_pvs = line.contains("=1"); if (line.contains("maximise_cling")) m_maximise_cling = line.contains("=1"); } } void LvmConfig::setActivation(const QStringList &variables) { for (auto line : variables) { if (line.contains("mirror_region_size")) { line.remove(0, 1 + line.indexOf('=')); m_mirror_region_size = line.toLongLong() * 1024; } } } QString LvmConfig::getMirrorSegtypeDefault() { return m_mirror_segtype_default; } bool LvmConfig::getMirrorLogsRequireSeparatePvs() { return m_mirror_logs_require_separate_pvs; } bool LvmConfig::getThinPoolMetadataRequireSeparatePvs() { return m_thin_pool_metadata_require_separate_pvs; } bool LvmConfig::getMaximiseCling() { return m_maximise_cling; } long long LvmConfig::getMirrorRegionSize() { return m_mirror_region_size; } kvpm-0.9.10/kvpm/physvol.cpp0000644000175000017500000002003412770324126016172 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "physvol.h" #include "logvol.h" #include "misc.h" #include "volgroup.h" #include #include #include namespace { bool isLessThan(const LVSegmentExtent * lv1 , const LVSegmentExtent * lv2) { return lv1->first_extent < lv2->first_extent; } } PhysVol::PhysVol(pv_t pv, const VolGroup *const vg) : m_vg(vg) { rescan(pv); } void PhysVol::rescan(pv_t lvm_pv) { QByteArray flags; lvm_property_value value; value = lvm_pv_get_property(lvm_pv, "pv_attr"); if (value.is_valid) flags.append(value.value.string, 3); value = lvm_pv_get_property(lvm_pv, "pv_tags"); m_tags.clear(); if (value.is_valid) m_tags = QString(value.value.string).split(','); for (int x = 0; x < m_tags.size(); x++) m_tags[x] = m_tags[x].trimmed(); value = lvm_pv_get_property(lvm_pv, "pv_mda_used_count"); if (value.is_valid) m_mda_used = value.value.integer; if (flags[0] == 'a') m_allocatable = true; else m_allocatable = false; if (flags[2] == 'm') m_missing = true; else m_missing = false; m_last_used_extent = 0; m_active = false; // pv is active if any associated lvs are active m_device = QString(lvm_pv_get_name(lvm_pv)); m_device_size = lvm_pv_get_dev_size(lvm_pv); m_unused = lvm_pv_get_free(lvm_pv); m_size = lvm_pv_get_size(lvm_pv); m_uuid = QString(lvm_pv_get_uuid(lvm_pv)); m_mda_count = lvm_pv_get_mda_count(lvm_pv); value = lvm_pv_get_property(lvm_pv, "pv_mda_size"); if (value.is_valid) m_mda_size = value.value.integer; m_mapper_device = findMapperPath(m_device); /* // The following wil be used to to calculate the last used // segement once the "lv_name" property gets implemented // Finding segments with lvs locked against change (snaps mirrors and pvmoves) // will also be possible // remove clunky code from pvtree.cpp when this get implemented! dm_list* pvseg_dm_list = lvm_pv_list_pvsegs(lvm_pv); lvm_pvseg_list *pvseg_list; if(pvseg_dm_list){ dm_list_iterate_items(pvseg_list, pvseg_dm_list){ value = lvm_pvseg_get_property( pvseg_list->pvseg , "lv_name"); qDebug() << "Name: " << value.value.string; value = lvm_pvseg_get_property( pvseg_list->pvseg , "lv_attr"); qDebug() << "attr: " << value.value.string; value = lvm_pvseg_get_property( pvseg_list->pvseg , "pvseg_start"); if(value.is_valid) qDebug() << "Seg start: " << value.value.integer; else qDebug() << "Not valid"; value = lvm_pvseg_get_property( pvseg_list->pvseg , "pvseg_size"); if(value.is_valid) qDebug() << "Seg size: " << value.value.integer; else qDebug() << "Not valid"; } } */ return; } int PhysVol::getPercentUsed() const { int percent; if (m_unused == 0) return 100; else if (m_unused == m_size) return 0; else if (m_size == 0) // This shouldn't happen return 100; else percent = qRound(((m_size - m_unused) * 100.0) / m_size); return percent; } // Returns a list of all the lv segments on the pv sorted by the // extent. Ordered from extent first to last extent. // Must not be called until after the LogVols have been scanned QList PhysVol::sortByExtent() const { QList lv_extents; for (auto lv : getVg()->getLogicalVolumesFlat()) { if (!lv->isSnapContainer()) { for (int seg = lv->getSegmentCount() - 1; seg >= 0; --seg) { QStringList pv_name_list = lv->getPvNames(seg); QList first_extent_list = lv->getSegmentStartingExtent(seg); for (int i = pv_name_list.size() - 1; i >= 0; --i) { if (pv_name_list[i] == getMapperName()) { LVSegmentExtent *const temp = new LVSegmentExtent; temp->lv_name = lv->getName(); temp->first_extent = first_extent_list[i]; temp->last_extent = temp->first_extent - 1 + (lv->getSegmentExtents(seg) / (lv->getSegmentStripes(seg))); lv_extents.append(temp); } } } } } qSort(lv_extents.begin() , lv_extents.end(), isLessThan); return lv_extents; } /* Find the contiguous additional free space on this pv for the specified lv. For thin pools, RAID and mirrors it drills down to the lower levels. Returns zero if the lv isn't using this pv already or it is only used for an element that normally isn't extensible such as a log or metadata */ long long PhysVol::getContiguous(LogVol *lv) const { long long contiguous = 0; const QList lv_extents = sortByExtent(); LvList legs; if (lv == nullptr) return getContiguous(); if (lv->isThinPool()) { for (auto data : lv->getAllChildrenFlat()) { if (data->isThinPoolData()) { lv = data; break; } } } if (lv->isMirror() || lv->isRaid()) { for (auto image : lv->getAllChildrenFlat()) { if (image->isRaidImage() || (image->isLvmMirrorLeg() && !image->isLvmMirrorLog())) legs.append(image); } } else { legs.append(lv); } const long long extent_size = m_vg->getExtentSize(); const long long end = (m_size / extent_size) - 1; for (auto leg : legs) { const int last_segment = leg->getSegmentCount() - 1; const QStringList pv_names = leg->getPvNames(last_segment); long long last_extent; for (int i = 0; i < pv_names.size(); ++i) { if (pv_names[i] == m_device) { last_extent = leg->getSegmentStartingExtent(last_segment)[i]; last_extent += (leg->getSegmentExtents(last_segment) / pv_names.size()) - 1; for (int j = 0; j < lv_extents.size(); ++j) { if (lv_extents[j]->first_extent > last_extent) { contiguous = (lv_extents[j]->first_extent - last_extent) - 1; break; } else if (j == lv_extents.size() - 1) { contiguous = end - last_extent; } } } } } for (auto ext : lv_extents) delete ext; return contiguous * extent_size; } long long PhysVol::getContiguous() const { long long contiguous = 0; const long long extent_size = m_vg->getExtentSize(); const long long end = (m_size / extent_size) - 1; const QList lv_extents = sortByExtent(); if (lv_extents.size() > 0) { for (int i = lv_extents.size() - 1; i >= 0 ; i--) { if (i == (lv_extents.size() - 1)) { if (end - lv_extents[i]->last_extent > contiguous) contiguous = end - lv_extents[i]->last_extent; } else { if ((lv_extents[i + 1]->first_extent - lv_extents[i]->last_extent) - 1 > contiguous) contiguous = (lv_extents[i + 1]->first_extent - lv_extents[i]->last_extent) - 1; } } if (lv_extents[0]->first_extent > contiguous) contiguous = lv_extents[0]->first_extent; } else { // empty pv contiguous = end + 1; } for (auto ext : lv_extents) delete ext; return contiguous * extent_size; } kvpm-0.9.10/kvpm/masterlist.h0000644000175000017500000000300412770324126016320 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef MASTERLIST_H #define MASTERLIST_H #include #include #include #include class VolGroup; class MountTables; class StorageDevice; class MasterList : public QObject { Q_OBJECT MountTables *m_mount_tables; static QList m_volume_groups; static QList m_storage_devices; static lvm_t m_lvm; static int m_LvmVersionMajor; static int m_LvmVersionMinor; static int m_LvmVersionPatchLevel; static int m_LvmVersionApi; void scanVolumeGroups(); void scanStorageDevices(); public: MasterList(); ~MasterList(); void rescan(); static int getLvmVersionMajor(); static int getLvmVersionMinor(); static int getLvmVersionPatchLevel(); static bool isLvmVersionEqualOrGreater(QString test_version); static int getLvmVersionApi(); static lvm_t getLvm(); static QList getVolGroups(); static QList getStorageDevices(); static int getVgCount(); static VolGroup *getVgByName(QString name); static QStringList getVgNames(); }; #endif kvpm-0.9.10/kvpm/mkfs.cpp0000644000175000017500000005665712770324126015452 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "mkfs.h" #include #include #include #include #include #include #include #include #include #include #include "logvol.h" #include "misc.h" #include "processprogress.h" #include "storagepartition.h" MkfsDialog::MkfsDialog(LogVol *const volume, QWidget *parent) : KvpmDialog(parent) { m_path = volume->getMapperPath(); m_stride_size = volume->getSegmentStripeSize(0); m_stride_count = volume->getSegmentStripes(0); if (hasInitialErrors(volume->isMounted())) preventExec(); else buildDialog(volume->getSize()); } MkfsDialog::MkfsDialog(StoragePartition *const partition, QWidget *parent) : KvpmDialog(parent) { m_path = partition->getName(); m_stride_size = 1; m_stride_count = 1; if (hasInitialErrors(partition->isMounted())) preventExec(); else buildDialog(partition->getSize()); } // Determines if there is any point to calling up the dialog at all bool MkfsDialog::hasInitialErrors(const bool mounted) { const QString warning_message = i18n("Writing a new file system on %1 " "will delete any existing data on it.", m_path); const QString error_message = i18n("The volume: %1 is mounted. It must be " "unmounted before a new filesystem " "can be written on it", m_path); if (mounted) { KMessageBox::sorry(nullptr, error_message); return true; } if (KMessageBox::warningContinueCancel(nullptr, warning_message, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) == KMessageBox::Continue) { return false; } return true; } void MkfsDialog::buildDialog(const long long size) { QWidget *const dialog_body = new QWidget; QVBoxLayout *const layout = new QVBoxLayout; dialog_body->setLayout(layout); QLabel *const label = new QLabel(i18n("Write or remove filesystem on: %1", m_path)); label->setAlignment(Qt::AlignCenter); layout->addSpacing(5); layout->addWidget(label); layout->addSpacing(5); m_tab_widget = new QTabWidget(this); m_tab_widget->addTab(generalTab(size), i18n("Filesystem Type")); m_tab_widget->addTab(advancedTab(), i18n("Standard Ext Options")); m_tab_widget->addTab(ext4Tab(), i18n("Additional Ext4 Options")); layout->addWidget(m_tab_widget); setMainWidget(dialog_body); setCaption(i18n("Write Filesystem")); enableOptions(true); connect(m_block_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(adjustStrideEdit(int))); } QWidget* MkfsDialog::generalTab(const long long size) { QWidget *const tab = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); QHBoxLayout *const upper_layout = new QHBoxLayout(); QHBoxLayout *const lower_layout = new QHBoxLayout(); layout->addLayout(upper_layout); layout->addLayout(lower_layout); QGroupBox *const radio_box = new QGroupBox(i18n("Select Filesystem")); QGridLayout *const radio_layout = new QGridLayout; radio_box->setLayout(radio_layout); ext2 = new QRadioButton("ext2", this); ext3 = new QRadioButton("ext3", this); ext4 = new QRadioButton("ext4", this); btrfs = new QRadioButton("btrfs", this); ntfs = new QRadioButton("ntfs", this); reiser = new QRadioButton("reiser", this); reiser4 = new QRadioButton("reiser4", this); jfs = new QRadioButton("jfs", this); xfs = new QRadioButton("xfs", this); swap = new QRadioButton(i18n("Linux swap"), this); vfat = new QRadioButton("ms-dos", this); wipefs = new QRadioButton("remove existing filesystem", this); m_wipe_fs_check = new QCheckBox("Remove existing filesystem before writing new one", this); m_wipe_fs_check->setChecked(true); radio_layout->addWidget(ext2, 1, 0); radio_layout->addWidget(ext3, 2, 0); radio_layout->addWidget(ext4, 3, 0); radio_layout->addWidget(btrfs, 4, 0); radio_layout->addWidget(ntfs, 4, 1); radio_layout->addWidget(reiser, 1, 1); radio_layout->addWidget(reiser4, 2, 1); radio_layout->addWidget(swap, 3, 1); radio_layout->addWidget(jfs, 1, 2); radio_layout->addWidget(xfs, 2, 2); radio_layout->addWidget(vfat, 3, 2); radio_layout->addWidget(wipefs, 4, 2); radio_layout->setRowStretch(5, 1); radio_layout->addWidget(m_wipe_fs_check, 6, 0, 1, -1, Qt::AlignLeft); ext4->setChecked(true); upper_layout->addWidget(radio_box); lower_layout->addStretch(); QHBoxLayout *const name_layout = new QHBoxLayout; QLabel *const name_label = new QLabel(i18n("Optional name or label: ")); name_layout->addWidget(name_label); m_name_edit = new QLineEdit(); name_label->setBuddy(m_name_edit); name_layout->addWidget(m_name_edit); name_layout->addStretch(); lower_layout->addLayout(name_layout); lower_layout->addStretch(); layout->addStretch(); if (size > 0x20000000000) { // 2TiB vfat->setChecked(false); vfat->setEnabled(false); if (size > 0x100000000000) { // 16TiB ext2->setChecked(false); ext2->setEnabled(false); ext3->setChecked(false); ext3->setEnabled(false); reiser->setChecked(false); reiser->setEnabled(false); } layout->addStretch(); QHBoxLayout *const info_layout = new QHBoxLayout; info_layout->addStretch(); QLabel *const icon_label = new QLabel(); icon_label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(32, 32)); info_layout->addWidget(icon_label); QLabel *const info_label = new QLabel(i18n("Some filesystems can not use a space this large and have been disabled.")); info_label->setWordWrap(true); info_layout->addWidget(info_label); info_layout->addStretch(); layout->addLayout(info_layout); layout->addStretch(); } tab->setLayout(layout); connect(wipefs, SIGNAL(toggled(bool)), this, SLOT(enableOptions(bool))); connect(ext2, SIGNAL(toggled(bool)), this, SLOT(enableOptions(bool))); connect(ext3, SIGNAL(toggled(bool)), this, SLOT(enableOptions(bool))); connect(ext4, SIGNAL(toggled(bool)), this, SLOT(enableOptions(bool))); return tab; } QWidget* MkfsDialog::advancedTab() { QWidget *const tab = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); QHBoxLayout *const top_layout = new QHBoxLayout(); top_layout->addStretch(); top_layout->addLayout(layout); top_layout->addStretch(); QLabel *const override_label = new QLabel(i18n("If enabled, these options override the mkfs defaults " "for ext2, ext3 and ext4 filesystems.")); QHBoxLayout *const label_layout = new QHBoxLayout(); layout->addSpacing(10); layout->addLayout(label_layout); layout->addSpacing(10); override_label->setWordWrap(true); override_label->setAlignment(Qt::AlignCenter); label_layout->addSpacing(40); label_layout->addWidget(override_label); label_layout->addSpacing(40); QHBoxLayout *const lower_layout = new QHBoxLayout(); layout->addLayout(lower_layout); QVBoxLayout *const left_layout = new QVBoxLayout; QVBoxLayout *const right_layout = new QVBoxLayout; lower_layout->addLayout(left_layout); lower_layout->addLayout(right_layout); m_stripe_box = stripeBox(); right_layout->addWidget(m_stripe_box); m_misc_options_box = miscOptionsBox(); m_base_options_box = baseOptionsBox(); left_layout->addWidget(m_base_options_box); right_layout->addWidget(m_misc_options_box); left_layout->addStretch(); right_layout->addStretch(); connect(m_sparse_super_check, SIGNAL(toggled(bool)), this, SLOT(enableOptions(bool))); connect(m_inode_edit, SIGNAL(textEdited(QString)), m_total_edit, SLOT(clear())); connect(m_total_edit, SIGNAL(textEdited(QString)), m_inode_edit, SLOT(clear())); tab->setLayout(top_layout); return tab; } QWidget* MkfsDialog::ext4Tab() { QWidget *const tab = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); QHBoxLayout *const lower_layout = new QHBoxLayout(); QLabel *const override_label = new QLabel(i18n("If enabled, these options override the mkfs defaults " "for settings that only apply to ext4 filesystems.")); QHBoxLayout *const label_layout = new QHBoxLayout(); layout->addSpacing(10); layout->addLayout(label_layout); layout->addSpacing(10); override_label->setWordWrap(true); override_label->setAlignment(Qt::AlignCenter); label_layout->addSpacing(40); label_layout->addWidget(override_label); label_layout->addSpacing(40); m_ext4_options_box = ext4OptionsBox(); lower_layout->addStretch(); lower_layout->addWidget(m_ext4_options_box); lower_layout->addStretch(); layout->addStretch(); layout->addLayout(lower_layout); layout->addStretch(); tab->setLayout(layout); return tab; } QGroupBox* MkfsDialog::miscOptionsBox() { QGroupBox *const misc_box = new QGroupBox("Inodes and Blocks"); misc_box->setCheckable(true); misc_box->setChecked(false); QVBoxLayout *const misc_layout = new QVBoxLayout; QHBoxLayout *const reserved_layout = new QHBoxLayout; QHBoxLayout *const block_layout = new QHBoxLayout; QHBoxLayout *const isize_layout = new QHBoxLayout; QHBoxLayout *const inode_layout = new QHBoxLayout; QHBoxLayout *const total_layout = new QHBoxLayout; QLabel *label; label = new QLabel(i18n("Reserved space: ")); reserved_layout->addWidget(label); m_reserved_spin = new QSpinBox; label->setBuddy(m_reserved_spin); m_reserved_spin->setRange(0, 100); m_reserved_spin->setValue(5); m_reserved_spin->setPrefix("%"); reserved_layout->addWidget(m_reserved_spin); reserved_layout->addStretch(); misc_layout->addLayout(reserved_layout); label = new QLabel(i18n("Block size: ")); block_layout->addWidget(label); m_block_combo = new QComboBox(); label->setBuddy(m_block_combo); m_block_combo->insertItem(0, i18nc("Let the program decide", "default")); m_block_combo->insertItem(1, "1024 KiB"); m_block_combo->insertItem(2, "2048 KiB"); m_block_combo->insertItem(3, "4096 KiB"); m_block_combo->setInsertPolicy(QComboBox::NoInsert); m_block_combo->setCurrentIndex(0); block_layout->addWidget(m_block_combo); block_layout->addStretch(); misc_layout->addLayout(block_layout); label = new QLabel(i18n("Inode size: ")); isize_layout->addWidget(label); m_inode_combo = new QComboBox(); label->setBuddy(m_inode_combo); m_inode_combo->insertItem(0, i18nc("Let the program decide", "default")); m_inode_combo->insertItem(1, "128 Bytes"); m_inode_combo->insertItem(2, "256 Bytes"); m_inode_combo->insertItem(3, "512 Bytes"); m_inode_combo->setInsertPolicy(QComboBox::NoInsert); m_inode_combo->setCurrentIndex(0); isize_layout->addWidget(m_inode_combo); isize_layout->addStretch(); misc_layout->addLayout(isize_layout); label = new QLabel(i18n("Bytes / inode: ")); inode_layout->addWidget(label); m_inode_edit = new QLineEdit(); m_inode_edit->setPlaceholderText(i18nc("Let the program decide", "default")); label->setBuddy(m_inode_edit); QIntValidator *inode_validator = new QIntValidator(m_inode_edit); m_inode_edit->setValidator(inode_validator); inode_validator->setBottom(0); inode_layout->addWidget(m_inode_edit); misc_layout->addLayout(inode_layout); label = new QLabel(i18n("Total inodes: ")); total_layout->addWidget(label); m_total_edit = new QLineEdit(); m_total_edit->setPlaceholderText(i18nc("Let the program decide", "default")); label->setBuddy(m_total_edit); QIntValidator *const total_validator = new QIntValidator(m_total_edit); m_total_edit->setValidator(total_validator); total_validator->setBottom(0); total_layout->addWidget(m_total_edit); misc_layout->addLayout(total_layout); misc_box->setLayout(misc_layout); return misc_box; } QGroupBox* MkfsDialog::ext4OptionsBox() { QGroupBox *const options_box = new QGroupBox("Ext4 Options"); QVBoxLayout *const options_layout = new QVBoxLayout(); options_box->setCheckable(true); options_box->setChecked(false); m_extent_check = new QCheckBox(i18n("Use extents")); m_flex_bg_check = new QCheckBox(i18n("Flexible block group layout")); m_huge_file_check = new QCheckBox(i18n("Enable files over 2TB")); m_uninit_bg_check = new QCheckBox(i18n("Don't init all block groups")); m_lazy_itable_init_check = new QCheckBox(i18n("Don't init all inodes")); m_dir_nlink_check = new QCheckBox(i18n("Unlimited subdirectories")); m_extra_isize_check = new QCheckBox(i18n("Nanosecond timestamps")); options_layout->addWidget(m_flex_bg_check); options_layout->addWidget(m_huge_file_check); options_layout->addWidget(m_uninit_bg_check); options_layout->addWidget(m_lazy_itable_init_check); options_layout->addWidget(m_dir_nlink_check); options_layout->addWidget(m_extra_isize_check); options_layout->addWidget(m_extent_check); options_box->setLayout(options_layout); return options_box; } QGroupBox* MkfsDialog::stripeBox() { QGroupBox *const stripe_box = new QGroupBox(i18n("Striping")); stripe_box->setCheckable(true); stripe_box->setChecked(false); QVBoxLayout *const stripe_layout = new QVBoxLayout(); stripe_box->setLayout(stripe_layout); QLabel *label; QHBoxLayout *const stride_layout = new QHBoxLayout; label = new QLabel(i18n("Stride size in blocks: ")); stride_layout->addWidget(label); m_stride_edit = new QLineEdit(QString("%1").arg(m_stride_size / 4096)); label->setBuddy(m_stride_edit); QIntValidator *const stride_validator = new QIntValidator(m_stride_edit); m_stride_edit->setValidator(stride_validator); stride_validator->setBottom(1); stride_layout->addWidget(m_stride_edit); stride_layout->addStretch(); stripe_layout->addLayout(stride_layout); QHBoxLayout *const count_layout = new QHBoxLayout; label = new QLabel(i18n("Strides per stripe: ")); count_layout->addWidget(label); m_count_edit = new QLineEdit(QString("%1").arg(m_stride_count)); label->setBuddy(m_count_edit); QIntValidator *const count_validator = new QIntValidator(m_count_edit); m_count_edit->setValidator(count_validator); count_validator->setBottom(1); count_layout->addWidget(m_count_edit); count_layout->addStretch(); stripe_layout->addLayout(count_layout); return stripe_box; } QGroupBox* MkfsDialog::baseOptionsBox() { QGroupBox *const options_box = new QGroupBox("Basic Options"); QVBoxLayout *const options_layout = new QVBoxLayout(); options_box->setCheckable(true); options_box->setChecked(false); m_ext_attr_check = new QCheckBox(i18n("Extended attributes")); m_resize_inode_check = new QCheckBox(i18n("Resize inode")); m_resize_inode_check->setEnabled(false); m_dir_index_check = new QCheckBox(i18n("Directory B-Tree index")); m_filetype_check = new QCheckBox(i18n("Store filetype in inode")); m_sparse_super_check = new QCheckBox(i18n("Sparse superblock")); options_layout->addWidget(m_ext_attr_check); options_layout->addWidget(m_resize_inode_check); options_layout->addWidget(m_dir_index_check); options_layout->addWidget(m_filetype_check); options_layout->addWidget(m_sparse_super_check); options_box->setLayout(options_layout); return options_box; } void MkfsDialog::enableOptions(bool) { if (wipefs->isChecked()) { m_wipe_fs_check->setChecked(false); m_wipe_fs_check->setEnabled(false); } else { m_wipe_fs_check->setChecked(true); m_wipe_fs_check->setEnabled(true); } if (ext2->isChecked() || ext3->isChecked() || ext4->isChecked()) { m_tab_widget->setTabEnabled(1, true); m_base_options_box->setEnabled(true); m_misc_options_box->setEnabled(true); if (ext4->isChecked()) { m_ext4_options_box->setEnabled(true); m_tab_widget->setTabEnabled(2, true); } else { m_ext4_options_box->setEnabled(false); m_ext4_options_box->setChecked(false); m_tab_widget->setTabEnabled(2, false); } } else { m_base_options_box->setEnabled(false); m_base_options_box->setChecked(false); m_ext4_options_box->setEnabled(false); m_ext4_options_box->setChecked(false); m_misc_options_box->setEnabled(false); m_tab_widget->setTabEnabled(1, false); m_tab_widget->setTabEnabled(2, false); } if (m_sparse_super_check->isChecked()) m_resize_inode_check->setEnabled(true); else { m_resize_inode_check->setEnabled(false); m_resize_inode_check->setChecked(false); } } void MkfsDialog::wipeFilesystem() { QStringList args = QStringList() << "wipefs" << "--all" << m_path; if (m_wipe_fs_check->isChecked() || wipefs->isChecked()) ProcessProgress wipefs(args, true); } void MkfsDialog::commit() { hide(); wipeFilesystem(); if (wipefs->isChecked()) return; QStringList arguments; QStringList mkfs_options; QStringList ext_options; QStringList extended_options; QString type; if (ext2->isChecked()) { type = "ext2"; } else if (ext3->isChecked()) { type = "ext3"; } else if (ext4->isChecked()) { type = "ext4"; } else if (btrfs->isChecked()) { type = "btrfs"; } else if (ntfs->isChecked()) { type = "ntfs"; mkfs_options << "-Q" << "--quiet"; } else if (reiser->isChecked()) { mkfs_options << "-q"; type = "reiserfs"; } else if (reiser4->isChecked()) { mkfs_options << "-y"; type = "reiser4"; } else if (jfs->isChecked()) { mkfs_options << "-q"; type = "jfs"; } else if (xfs->isChecked()) { mkfs_options << "-q" << "-f"; type = "xfs"; } else if (swap->isChecked()) { type = "swap"; } else if (vfat->isChecked()) { type = "vfat"; } else { type = "ext3"; qDebug() << "Reached the default else in mkfs.cpp. How did that happen?"; } if (type == "reiserfs") { if (!(m_name_edit->text()).isEmpty()) mkfs_options << "--label" << m_name_edit->text(); } else if (type == "vfat") { if (!(m_name_edit->text()).isEmpty()) mkfs_options << "-n" << m_name_edit->text(); } else if ((type == "reiser4") || (type == "jfs") || (type == "xfs") || (type == "btrfs") || (type == "ntfs")) { if (!(m_name_edit->text()).isEmpty()) mkfs_options << "-L" << m_name_edit->text(); } else if ((type == "ext2") || (type == "ext3") || (type == "ext4")) { if (m_misc_options_box->isChecked()) { mkfs_options << "-m" << QString("%1").arg(m_reserved_spin->value()); if (m_block_combo->currentIndex() > 0) mkfs_options << "-b" << m_block_combo->currentText().remove("KiB").trimmed(); if (!m_inode_edit->text().isEmpty()) mkfs_options << "-i" << m_inode_edit->text(); else if (!m_total_edit->text().isEmpty()) mkfs_options << "-N" << m_total_edit->text(); if (m_inode_combo->currentIndex() > 0) mkfs_options << "-I" << m_inode_combo->currentText().remove("Bytes").trimmed(); } if (m_stripe_box->isChecked() || m_ext4_options_box->isChecked()) { mkfs_options << "-E"; if (m_stripe_box->isChecked()) { extended_options << QString("stride=%1").arg(m_stride_edit->text()); extended_options << QString("stripe_width=%1").arg(m_count_edit->text()); } if (m_ext4_options_box->isChecked()) { if (m_lazy_itable_init_check->isChecked()) extended_options << "lazy_itable_init=1"; else extended_options << "lazy_itable_init=0"; } mkfs_options << extended_options.join(","); } if (!(m_name_edit->text()).isEmpty()) mkfs_options << "-L" << m_name_edit->text(); if (m_base_options_box->isChecked()) { if (m_ext_attr_check->isChecked()) ext_options << "ext_attr"; else ext_options << "^ext_attr"; if (m_resize_inode_check->isChecked()) ext_options << "resize_inode"; else ext_options << "^resize_inode"; if (m_dir_index_check->isChecked()) ext_options << "dir_index"; else ext_options << "^dir_index"; if (m_filetype_check->isChecked()) ext_options << "filetype"; else ext_options << "^filetype"; if (m_sparse_super_check->isChecked()) ext_options << "sparse_super"; else ext_options << "^sparse_super"; } if (m_ext4_options_box->isChecked() && ext4->isChecked()) { if (m_extent_check->isChecked()) ext_options << "extent"; else ext_options << "^extent"; if (m_flex_bg_check->isChecked()) ext_options << "flex_bg"; else ext_options << "^flex_bg"; if (m_huge_file_check->isChecked()) ext_options << "huge_file"; else ext_options << "^huge_file"; if (m_uninit_bg_check->isChecked()) ext_options << "uninit_bg"; else ext_options << "^uninit_bg"; if (m_dir_nlink_check->isChecked()) ext_options << "dir_nlink"; else ext_options << "^dir_nlink"; if (m_extra_isize_check->isChecked()) ext_options << "extra_isize"; else ext_options << "^extra_isize"; } } if (type != "swap") { arguments << "mkfs" << "-t" << type;; if (mkfs_options.size()) arguments << mkfs_options; if (ext_options.size()) arguments << "-O" << ext_options.join(","); arguments << m_path; } else { arguments << "mkswap" << m_path; } ProcessProgress mkfs(arguments, true); } void MkfsDialog::adjustStrideEdit(int index) { long stride = m_stride_size; if (index == 1) stride /= 1024; else if (index == 2) stride /= 2048; else stride /= 4096; m_stride_edit->setText(QString("%1").arg(stride)); } kvpm-0.9.10/kvpm/vgmerge.cpp0000644000175000017500000001013712770324126016125 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011, 2012, 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgmerge.h" #include "logvol.h" #include "masterlist.h" #include "processprogress.h" #include "volgroup.h" #include #include #include #include #include #include #include #include #include VGMergeDialog::VGMergeDialog(VolGroup *const volumeGroup, QWidget *parent) : KvpmDialog(parent), m_vg(volumeGroup) { setCaption(i18n("Merge Volume Group")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); QLabel *name_label = new QLabel(i18n("Merge volume group: %1", m_vg->getName())); name_label->setAlignment(Qt::AlignCenter); layout->addWidget(name_label); layout->addSpacing(10); QGroupBox *const target_group = new QGroupBox(); QVBoxLayout *target_layout = new QVBoxLayout(); QHBoxLayout *const combo_layout = new QHBoxLayout(); QLabel *const target_label = new QLabel(i18n("Merge with:")); combo_layout->addWidget(target_label); m_target_combo = new QComboBox(); target_label->setBuddy(m_target_combo); QList groups = MasterList::getVolGroups(); for (int x = 0; x < groups.size(); x++) { // remove this groups own name from list if (m_vg->getName() != groups[x]->getName()) { m_target_combo->addItem(groups[x]->getName()); m_extent_size.append(groups[x]->getExtentSize()); } } combo_layout->addWidget(m_target_combo); m_autobackup = new QCheckBox("autobackup"); m_autobackup->setChecked(true); target_layout->addLayout(combo_layout); m_error_stack = new QStackedWidget(); QWidget *const no_error_widget = new QWidget(); QWidget *const error_widget = new QWidget(); QHBoxLayout *const error_layout = new QHBoxLayout(); QLabel *const icon_label = new QLabel(); icon_label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(32, 32)); error_layout->addWidget(icon_label); error_layout->addWidget(new QLabel(i18n("Error: Extent size must match"))); error_widget->setLayout(error_layout); m_error_stack->addWidget(no_error_widget); m_error_stack->addWidget(error_widget); target_layout->addWidget(m_error_stack); target_group->setLayout(target_layout); layout->addWidget(target_group); layout->addWidget(m_autobackup); connect(m_target_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(compareExtentSize())); checkSanity(); compareExtentSize(); } void VGMergeDialog::commit() { QStringList args = QStringList() << "vgmerge"; if (m_autobackup->isChecked()) args << "--autobackup" << "y"; else args << "--autobackup" << "n"; args << m_target_combo->currentText() << m_vg->getName(); ProcessProgress vgmerge(args); } void VGMergeDialog::checkSanity() { if (MasterList::getVgNames().size() < 2) { KMessageBox::sorry(nullptr, i18n("There is no other volume group to merge with")); preventExec(); return; } for (auto lv : m_vg->getLogicalVolumes()) { if (lv->isActive()) { KMessageBox::sorry(nullptr, i18n("The volume group to merge must not have active logical volumes")); preventExec(); return; } } } void VGMergeDialog::compareExtentSize() { if (m_extent_size.size() > 0){ if (m_extent_size[ m_target_combo->currentIndex() ] == m_vg->getExtentSize()) { m_error_stack->setCurrentIndex(0); enableButtonOk(true); } else { m_error_stack->setCurrentIndex(1); enableButtonOk(false); } } } kvpm-0.9.10/kvpm/pvactionsmenu.cpp0000644000175000017500000000133412770324126017363 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvactionsmenu.h" #include "physvol.h" #include "pvactions.h" #include "volgroup.h" #include PVActionsMenu::PVActionsMenu(PVActions *const pvactions, QWidget *parent) : QMenu(parent) { addAction(pvactions->action("pvmove")); addAction(pvactions->action("pvremove")); addAction(pvactions->action("pvchange")); } kvpm-0.9.10/kvpm/sizeselectorbox.h0000644000175000017500000000544512770324126017370 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2012 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef SIZESELECTORBOX_H #define SIZESELECTORBOX_H #include #include #include #include #include #include class SizeSelectorBox : public QGroupBox { Q_OBJECT long long m_max_size; // max size possible in sectors or extents, not bytes etc. long long m_min_size; long long m_constrained_max; // working max less than or equal to m_max_size long long m_constrained_min; long long m_current_size; // current size in sectors or extents, not bytes etc. long long m_initial_size; // Starting size in sectors or extents, not bytes etc. long long m_unit_size; // the size of the extents or sectors in bytes QSlider *m_size_slider; QComboBox *m_suffix_combo; QLineEdit *m_size_edit; QDoubleValidator *m_size_validator; QCheckBox *m_shrink_box; // Allow partition/volume to shrink. QCheckBox *m_size_box; // Lock size to change QCheckBox *m_offset_box; // Lock offset to change. bool m_is_volume; // Is it a volume or a partition? bool m_is_offset; // Are we selecting the starting point offset of a partition? bool m_is_new; // New volume/partition, not resizing old one? bool m_start_locked; // Start out with the size check box checked bool m_is_valid; // Valid data? bool m_use_si_units; long long convertSizeToUnits(int index, double size); // ie: convert MiBs to sectors or extents void updateValidator(); void updateSlider(); void setConstraints(bool unlock); signals: void stateChanged(); public: SizeSelectorBox(long long unitSize, long long minSize, long long maxSize, long long initialSize, // size in extents bool isVolume, bool isOffset, bool isNew = false, bool startLocked = false, QWidget *parent = 0); long long getNewSize(); long long getMinimumSize(); long long getMaximumSize(); void resetToInitial(); void setConstrainedMax(long long max); bool isLocked(); bool setNewSize(long long size); // return false if size is outside min or max bool isValid(); bool usingBytes(); // Whether the combo set to bytes (MiB, GiB or TiB) vs. extents private slots: void setToSlider(int value); void setToEdit(QString size); void updateEdit(); void lock(bool lock); void lockShrink(bool lock); void disableLockShrink(bool disable); }; #endif kvpm-0.9.10/kvpm/pedexceptions.cpp0000644000175000017500000000445212770324126017346 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011, 2012, 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pedexceptions.h" #include #include #include #include /* If the error being handeled has a question we put up a Yes/No warning box. Otherwise we put up a "sorry dialog" for warnings and errors or an "error dialog" for bugs, fatal errors and missing features. Generally a "Yes" choice will tell Ped to fix the problem while "No" will ignore it. */ PedExceptionOption my_handler(PedException *exception) { QString message(exception->message); if (message.contains("unrecognised disk label")) return PED_EXCEPTION_CANCEL; qApp->restoreOverrideCursor(); // reset the cursor to not-busy const PedExceptionOption options = exception->options; if (exception->type == PED_EXCEPTION_INFORMATION){ KMessageBox::information(nullptr, message); return PED_EXCEPTION_CANCEL; } else if (exception->type == PED_EXCEPTION_WARNING || exception->type == PED_EXCEPTION_ERROR) { if (options & (PED_EXCEPTION_FIX | PED_EXCEPTION_YES)) { if (KMessageBox::warningYesNo(nullptr, message) == KMessageBox::Yes) { if (options & PED_EXCEPTION_FIX) return PED_EXCEPTION_FIX; else return PED_EXCEPTION_YES; } else { if (options & PED_EXCEPTION_IGNORE) return PED_EXCEPTION_IGNORE; else if (options & PED_EXCEPTION_NO) return PED_EXCEPTION_NO; else if (options & PED_EXCEPTION_OK) return PED_EXCEPTION_OK; else return PED_EXCEPTION_CANCEL; } } else { KMessageBox::sorry(nullptr, message); } } else { KMessageBox::error(nullptr, message); return PED_EXCEPTION_CANCEL; } return PED_EXCEPTION_CANCEL; } kvpm-0.9.10/kvpm/lvsizechart.h0000644000175000017500000000241412770324126016473 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVSIZECHART_H #define LVSIZECHART_H #include class QHBoxLayout; class QResizeEvent; class QTreeWidget; class VolGroup; class LogVol; class LVChartSeg; class LVSizeChart : public QFrame { Q_OBJECT VolGroup *m_vg = nullptr; QTreeWidget *m_vg_tree = nullptr; QHBoxLayout *m_layout = nullptr; QList m_widgets; // These are segments of the bar chart QList m_ratios; // These are the relative size of each segment // to the whole chart. The total should be about 1. QFrame *frameAndConnect(LVChartSeg *const seg); public: LVSizeChart(VolGroup *const group, QTreeWidget *const vgTree, QWidget *parent = nullptr); void populateChart(); void resizeEvent(QResizeEvent *event); private slots: void vgtreeClicked(); signals: void lvMenuRequested(LogVol *lv); }; #endif kvpm-0.9.10/kvpm/vgexport.cpp0000644000175000017500000000167712770324126016360 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgexport.h" #include #include #include #include "logvol.h" #include "processprogress.h" #include "volgroup.h" bool export_vg(VolGroup *const volumeGroup) { QStringList args; const QString message = i18n("Export volume group: %1?", volumeGroup->getName()); if (KMessageBox::questionYesNo(0, message) == KMessageBox::Yes) { args << "vgexport" << volumeGroup->getName(); ProcessProgress remove(args); return true; } else return false; } kvpm-0.9.10/kvpm/lvactionsmenu.h0000644000175000017500000000115212770324126017022 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVACTIONSMENU_H #define LVACTIONSMENU_H #include class LVActions; class LVActionsMenu : public QMenu { Q_OBJECT public: LVActionsMenu(LVActions *const lvactions, QWidget *parent); }; #endif kvpm-0.9.10/kvpm/vgcreate.h0000644000175000017500000000326512770324126015742 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGCREATE_H #define VGCREATE_H #include #include "kvpmdialog.h" class QComboBox; class QLabel; class QLineEdit; class QCheckBox; class QRegExpValidator; class StorageBase; class PvGroupBox; class VGCreateDialog : public KvpmDialog { Q_OBJECT QLabel *m_pv_label, *m_total_available_label, *m_total_selected_label; PvGroupBox *m_pv_checkbox = nullptr; QLineEdit *m_vg_name, *m_max_lvs, *m_max_pvs, *m_size_edit, *m_align_edit, *m_offset_edit; QCheckBox *m_clustered, *m_auto_backup, *m_max_lvs_check, *m_max_pvs_check; QComboBox *m_extent_size, *m_extent_suffix, *m_copies_combo; QRegExpValidator *m_validator = nullptr; void limitExtentSize(int); bool continueWarning(); void buildDialog(QList devices); QWidget *buildGeneralTab(const QList devices); QWidget *buildAdvancedTab(); public: explicit VGCreateDialog(QWidget *parent = nullptr); explicit VGCreateDialog(StorageBase *const device, QWidget *parent = nullptr); private slots: void validateOK(); void commit(); void extentSizeChanged(); }; #endif kvpm-0.9.10/kvpm/pvmove.h0000644000175000017500000000417012770324126015452 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVMOVE_H #define PVMOVE_H #include "kvpmdialog.h" #include #include class QCheckBox; class QGroupBox; class QLabel; class QRadioButton; class QString; class LogVol; class NameAndRange; class NoMungeRadioButton; class PhysVol; class PvGroupBox; class VolGroup; bool restart_pvmove(); bool stop_pvmove(); class PVMoveDialog : public KvpmDialog { Q_OBJECT const VolGroup *m_vg = nullptr; LogVol *m_lv = nullptr; int m_segment; bool m_move_segment; long long m_pv_used_space; QList m_sources; QList m_radio_buttons; // user can select only one source pv PvGroupBox *m_pv_box = nullptr; // many target pvs may be selected QLabel *m_radio_label = nullptr; // number of extents selected for the pvmove source void buildDialog(QList targets); void setupSegmentMove(int segment); void setupFullMove(); bool hasMovableExtents(); QStringList arguments(); QStringList getLvNames(); QList removeForbiddenTargets(QList targets, const QString source); QList removeFullTargets(QList targets); QStringList getForbiddenTargets(LogVol *const lv, const QString source); QWidget* singleSourceWidget(); QWidget* bannerWidget(); bool isMovable(LogVol *lv); long long movableExtents(); public: explicit PVMoveDialog(PhysVol *const physicalVolume, QWidget *parent = nullptr); explicit PVMoveDialog(LogVol *const logicalVolume, int const segment = -1, QWidget *parent = nullptr); ~PVMoveDialog(); private slots: void commit(); void disableTargets(); void resetOkButton(); void setRadioExtents(); }; #endif kvpm-0.9.10/kvpm/pvproperties.cpp0000644000175000017500000001526412770324126017241 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvproperties.h" #include #include #include #include #include #include #include #include "masterlist.h" #include "physvol.h" #include "logvol.h" #include "volgroup.h" PVProperties::PVProperties(PhysVol *const volume, QWidget *parent) : QWidget(parent), m_pv(volume) { setBackgroundRole(QPalette::Base); setAutoFillBackground(true); KConfigSkeleton skeleton; bool mda, uuid; skeleton.setCurrentGroup("PhysicalVolumeProperties"); skeleton.addItemBool("pp_mda", mda, true); skeleton.addItemBool("pp_uuid", uuid, false); QVBoxLayout *const top_layout = new QVBoxLayout; top_layout->setSpacing(2); top_layout->setMargin(2); top_layout->addWidget(buildLvBox()); if (mda) top_layout->addWidget(buildMdaBox()); if (uuid) top_layout->addWidget(buildUuidBox()); top_layout->addStretch(); setLayout(top_layout); } QFrame *PVProperties::buildMdaBox() { bool use_si_units; KConfigSkeleton skeleton; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", use_si_units, false); KFormat::BinaryUnitDialect dialect; if (use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; QLabel *label; QHBoxLayout *const layout = new QHBoxLayout; QFrame *const frame = new QFrame; frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); label = new QLabel("MDA"); label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); layout->addWidget(label); label = new QLabel(QString("Total: %1").arg(m_pv->getMdaCount())); layout->addWidget(label); label = new QLabel(QString("In use: %1").arg(m_pv->getMdaUsed())); layout->addWidget(label); label = new QLabel(QString("Size: %1").arg(KFormat().formatByteSize(m_pv->getMdaSize(), 1, dialect))); layout->addWidget(label); frame->setLayout(layout); return frame; } QFrame *PVProperties::buildLvBox() { QGridLayout *const layout = new QGridLayout; QLabel *label; long long first_extent; long long last_extent; QFrame *const frame = new QFrame; frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); label = new QLabel(i18n("Volume name")); label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); layout->addWidget(label, 1, 0); label = new QLabel(i18n("Start")); label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); layout->addWidget(label, 1, 1); label = new QLabel(i18n("End")); label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); layout->addWidget(label, 1, 2); label = new QLabel(i18n("Extents")); label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); layout->addWidget(label, 1, 3); const QList lv_extents = m_pv->sortByExtent(); int row = 2; for (int x = 0; x < lv_extents.size() ; x++) { first_extent = lv_extents[x]->first_extent; last_extent = lv_extents[x]->last_extent; if (row % 2 == 0) { label = new QLabel(lv_extents[x]->lv_name); label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); label->setBackgroundRole(QPalette::Base); label->setAutoFillBackground(true); layout->addWidget(label, row, 0); label = new QLabel(i18n("%1", first_extent)); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); label->setBackgroundRole(QPalette::Base); label->setAutoFillBackground(true); layout->addWidget(label, row, 1); label = new QLabel(i18n("%1", last_extent)); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); label->setBackgroundRole(QPalette::Base); label->setAutoFillBackground(true); layout->addWidget(label, row, 2); label = new QLabel(i18n("%1", last_extent - first_extent + 1)); label->setBackgroundRole(QPalette::Base); label->setAutoFillBackground(true); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); layout->addWidget(label, row, 3); } else { label = new QLabel(lv_extents[x]->lv_name); label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); label->setBackgroundRole(QPalette::AlternateBase); label->setAutoFillBackground(true); layout->addWidget(label, row, 0); label = new QLabel(i18n("%1", first_extent)); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); label->setBackgroundRole(QPalette::AlternateBase); label->setAutoFillBackground(true); layout->addWidget(label, row, 1); label = new QLabel(i18n("%1", last_extent)); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); label->setBackgroundRole(QPalette::AlternateBase); label->setAutoFillBackground(true); layout->addWidget(label, row, 2); label = new QLabel(i18n("%1", last_extent - first_extent + 1)); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); label->setBackgroundRole(QPalette::AlternateBase); label->setAutoFillBackground(true); layout->addWidget(label, row, 3); } row++; } label = new QLabel(); layout->addWidget(label, row + 1, 0, 1, -1); label = new QLabel(i18n("Total extents: %1", m_pv->getSize() / m_pv->getVg()->getExtentSize())); layout->addWidget(label, row + 2, 0, 1, -1); for (int x = 0; x < lv_extents.size(); x++) delete lv_extents[x]; frame->setLayout(layout); return frame; } QFrame *PVProperties::buildUuidBox() { QLabel *label; QHBoxLayout *const layout = new QHBoxLayout; QFrame *const frame = new QFrame; frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); label = new QLabel("UUID"); label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); layout->addWidget(label); label = new QLabel(m_pv->getUuid()); label->setAlignment(Qt::AlignCenter); label->setWordWrap(true); layout->addWidget(label); frame->setLayout(layout); return frame; } kvpm-0.9.10/kvpm/externalraid.h0000644000175000017500000000136012770324126016616 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2014 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef DMRAID_H #define DMRAID_H #include /* The purpose here is to find the names of raid devices created by device mapper or the multiple devices RAID driver and the names of the underlying block devices. */ void dmraid_get_devices(QStringList &block, QStringList &raid); void mdraid_get_devices(QStringList &block, QStringList &raid); #endif kvpm-0.9.10/kvpm/deviceactions.h0000644000175000017500000000233112770324126016753 0ustar benscottbenscott/* * * * Copyright (C) 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef DEVICEACTIONS_H #define DEVICEACTIONS_H #include #include class QActionGroup; class QTreeWidgetItem; class StoragePartition; class StorageDevice; class DeviceActions : public KActionCollection { Q_OBJECT StoragePartition *m_part = nullptr; StorageDevice *m_dev = nullptr; QActionGroup *m_act_grp = nullptr; void vgextendEnable(bool enable); public: explicit DeviceActions(QWidget *parent = nullptr); public slots: void changeDevice(QTreeWidgetItem *item); private slots: void extendVg(QAction *action); void checkFs(); void makeFs(); void maxFs(); void addPartition(); void changePartition(); void changeFlags(); void removePartition(); void createVg(); void createTable(); void reduceVg(); void mountFs(); void unmountFs(); }; #endif kvpm-0.9.10/kvpm/deviceproperties.h0000644000175000017500000000214212770324126017507 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2011 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef DEVICEPROPERTIES_H #define DEVICEPROPERTIES_H #include class StorageDevice; class StoragePartition; class PhysVol; class DeviceProperties : public QWidget { QFrame *hardwareFrame(StorageDevice *const device); QFrame *pvFrame(PhysVol *const pv); QFrame *mpFrame(StoragePartition *const partition); QFrame *fsFrame(StoragePartition *const partition, const bool showFsUuid, const bool showFsLabel); QFrame *generalFrame(StorageDevice *const device); QFrame *generalFrame(StoragePartition *const partition); public: explicit DeviceProperties(StorageDevice *const device, QWidget *parent = 0); explicit DeviceProperties(StoragePartition *const partition, QWidget *parent = 0); }; #endif kvpm-0.9.10/kvpm/kvpmconfigdialog.cpp0000644000175000017500000012412612770324126020020 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "kvpmconfigdialog.h" #include "executablefinder.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KvpmConfigDialog::KvpmConfigDialog(QWidget *parent, const QString name, KConfigSkeleton *const skeleton, ExecutableFinder *const executableFinder) : KConfigDialog(parent, name, skeleton), m_skeleton(skeleton), m_executable_finder(executableFinder) { setFaceType(KPageDialog::Auto); button(QDialogButtonBox::Help)->setDefault(true); addPage(generalPage(), i18nc("The standard common options", "General"), QString("configure")); addPage(colorsPage(), i18n("Colors"), QString("color-picker")); addPage(programsPage(), i18n("Programs"), QString("applications-system")); } KvpmConfigDialog::~KvpmConfigDialog() { m_skeleton->deleteLater(); } QTabWidget *KvpmConfigDialog::generalPage() { QTabWidget *const tabwidget = new QTabWidget; tabwidget->insertTab(1, treesTab(), i18n("Tree Views")); tabwidget->insertTab(1, propertiesTab(), i18n("Property Panels")); return tabwidget; } QWidget *KvpmConfigDialog::treesTab() { QWidget *const trees = new QWidget; QVBoxLayout *const layout = new QVBoxLayout(); QLabel *const banner = new QLabel(i18n("Set columns to show in tables and tree views")); banner->setAlignment(Qt::AlignCenter); layout->addWidget(banner); QHBoxLayout *const horizontal_layout = new QHBoxLayout(); horizontal_layout->addWidget(deviceGroup()); horizontal_layout->addWidget(logicalGroup()); horizontal_layout->addWidget(physicalGroup()); horizontal_layout->addWidget(allGroup()); layout->addLayout(horizontal_layout); trees->setLayout(layout); return trees; } QWidget *KvpmConfigDialog::propertiesTab() { QWidget *const properties = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); QLabel *const banner = new QLabel(i18n("Set information to show in property panels")); banner->setAlignment(Qt::AlignCenter); layout->addWidget(banner); QHBoxLayout *const horizontal_layout = new QHBoxLayout(); horizontal_layout->addWidget(devicePropertiesGroup()); horizontal_layout->addWidget(lvPropertiesGroup()); horizontal_layout->addWidget(pvPropertiesGroup()); layout->addLayout(horizontal_layout); properties->setLayout(layout); return properties; } QTabWidget *KvpmConfigDialog::programsPage() { m_executables_table = new QTableWidget(); m_executables_table->setColumnCount(2); const QStringList headers = QStringList() << i18n("Program name") << i18n("Full path"); m_executables_table->setHorizontalHeaderLabels(headers); QTabWidget *const programs = new QTabWidget; QVBoxLayout *const programs1_layout = new QVBoxLayout(); QVBoxLayout *const programs2_layout = new QVBoxLayout(); QWidget *const programs1 = new QWidget(); QWidget *const programs2 = new QWidget(); programs1->setLayout(programs1_layout); programs2->setLayout(programs2_layout); const QStringList default_entries = QStringList() << "/sbin/" << "/usr/sbin/" << "/bin/" << "/usr/bin/" << "/usr/local/bin/" << "/usr/local/sbin/"; static QStringList search_entries; m_skeleton->setCurrentGroup("SystemPaths"); m_skeleton->addItemStringList("SearchPath", search_entries, default_entries); m_edit_list = new KEditListWidget(); m_edit_list->setObjectName("kcfg_SearchPath"); QLabel *search_label = new QLabel(i18n("Set the search path for support programs")); search_label->setAlignment(Qt::AlignCenter); programs1_layout->addWidget(search_label); programs1_layout->addWidget(m_edit_list); programs->insertTab(1, programs1, "Search path"); m_edit_list->insertStringList(search_entries); programs2_layout->addWidget(m_executables_table); programs->insertTab(1, programs2, "Executables"); fillExecutablesTable(); return programs; } void KvpmConfigDialog::updateSettings() { QStringList search = m_edit_list->items(); // system paths to search for binaries search.removeDuplicates(); m_edit_list->clear(); for (int x = 0; x < search.size(); x++) { if (!search[x].endsWith('/')) search[x].append('/'); } m_edit_list->insertStringList(search); KConfigDialog::updateSettings(); m_executable_finder->reload(search); fillExecutablesTable(); } void KvpmConfigDialog::fillExecutablesTable() { QTableWidgetItem *table_item = nullptr; const QStringList all_names = m_executable_finder->getAllNames(); const QStringList all_paths = m_executable_finder->getAllPaths(); const QStringList not_found = m_executable_finder->getNotFound(); m_executables_table->clearContents(); m_executables_table->setRowCount(all_names.size() + not_found.size()); for (int x = 0; x < not_found.size(); x++) { table_item = new QTableWidgetItem(not_found[x]); m_executables_table->setItem(x, 0, table_item); table_item = new QTableWidgetItem(QIcon::fromTheme(QStringLiteral("dialog-error")), "Not Found"); m_executables_table->setItem(x, 1, table_item); } // these lists should be the same length, but just in case... for (int x = 0; (x < all_names.size()) && (x < all_paths.size()); x++) { table_item = new QTableWidgetItem(all_names[x]); m_executables_table->setItem(x + not_found.size(), 0, table_item); table_item = new QTableWidgetItem(all_paths[x]); m_executables_table->setItem(x + not_found.size(), 1, table_item); } m_executables_table->resizeColumnsToContents(); m_executables_table->setAlternatingRowColors(true); m_executables_table->verticalHeader()->hide(); } QGroupBox *KvpmConfigDialog::deviceGroup() { QGroupBox *const device_group = new QGroupBox(i18n("Device Tree")); QVBoxLayout *const device_layout = new QVBoxLayout(); device_group->setLayout(device_layout); static bool device_column; static bool partition_column; static bool capacity_column; static bool devremaining_column; static bool usage_column; static bool group_column; static bool flags_column; static bool mount_column; static bool expand_parts; m_skeleton->setCurrentGroup("DeviceTreeColumns"); // dt == device tree m_skeleton->addItemBool("dt_device", device_column, true); m_skeleton->addItemBool("dt_partition", partition_column, true); m_skeleton->addItemBool("dt_capacity", capacity_column, true); m_skeleton->addItemBool("dt_remaining", devremaining_column, true); m_skeleton->addItemBool("dt_usage", usage_column, true); m_skeleton->addItemBool("dt_group", group_column, true); m_skeleton->addItemBool("dt_flags", flags_column, true); m_skeleton->addItemBool("dt_mount", mount_column, true); m_skeleton->addItemBool("dt_expandparts", expand_parts, true); QCheckBox *const device_check = new QCheckBox(i18n("Device name")); QCheckBox *const partition_check = new QCheckBox(i18n("Partition type")); QCheckBox *const capacity_check = new QCheckBox(i18n("Capacity")); QCheckBox *const devremaining_check = new QCheckBox(i18n("Remaining space")); QCheckBox *const usage_check = new QCheckBox(i18n("Usage of device")); QCheckBox *const group_check = new QCheckBox(i18n("Volume group")); QCheckBox *const flags_check = new QCheckBox(i18n("Partition flags")); QCheckBox *const mount_check = new QCheckBox(i18n("Mount point")); QCheckBox *const expandparts_check = new QCheckBox(i18n("Expand device tree")); device_check->setObjectName("kcfg_dt_device"); partition_check->setObjectName("kcfg_dt_partition"); capacity_check->setObjectName("kcfg_dt_capacity"); devremaining_check->setObjectName("kcfg_dt_remaining"); usage_check->setObjectName("kcfg_dt_usage"); group_check->setObjectName("kcfg_dt_group"); flags_check->setObjectName("kcfg_dt_flags"); mount_check->setObjectName("kcfg_dt_mount"); expandparts_check->setObjectName("kcfg_dt_expandparts"); device_check->setToolTip(i18n("Show the path to the device, /dev/sda1 for example.")); partition_check->setToolTip(i18n("Show the type of partition, 'extended' for example.")); capacity_check->setToolTip(i18n("Show the storage capacity in megabytes, gigabytes or terabytes.")); devremaining_check->setToolTip(i18n("Show the remaining storage in megabytes, gigabytes or terabytes.")); usage_check->setToolTip(i18n("Show how the partition is being used. Usually the type of filesystem, such as ext4, \n" "swap space or as a physical volume.")); group_check->setToolTip(i18n("If the partition is a physical volume this column shows the volume group it is in.")); flags_check->setToolTip(i18n("Show any flags, such a 'boot.'")); mount_check->setToolTip(i18n("Show the mount point if the partition has a mounted filesystem.")); expandparts_check->setToolTip(i18n("This determines if all partitions of all devices get shown at start up. \n " "The user can still expand or collapse the items by clicking on them.")); device_layout->addWidget(device_check); device_layout->addWidget(partition_check); device_layout->addWidget(capacity_check); device_layout->addWidget(devremaining_check); device_layout->addWidget(usage_check); device_layout->addWidget(group_check); device_layout->addWidget(flags_check); device_layout->addWidget(mount_check); device_layout->addWidget(new KSeparator(Qt::Horizontal)); QLabel *const label = new QLabel(i18n("At start up:")); label->setAlignment(Qt::AlignCenter); device_layout->addWidget(label); device_layout->addWidget(expandparts_check); device_layout->addStretch(); return device_group; } QGroupBox *KvpmConfigDialog::physicalGroup() { QGroupBox *const physical_group = new QGroupBox(i18n("Physical Volume Table")); QVBoxLayout *const physical_layout = new QVBoxLayout(); physical_group->setLayout(physical_layout); static bool name_column; static bool size_column; static bool remaining_column; static bool used_column; static bool state_column; static bool allocate_column; static bool tags_column; static bool lvnames_column; m_skeleton->setCurrentGroup("PhysicalTreeColumns"); // pt == physical tree m_skeleton->addItemBool("pt_name", name_column, true); m_skeleton->addItemBool("pt_size", size_column, true); m_skeleton->addItemBool("pt_remaining", remaining_column, true); m_skeleton->addItemBool("pt_used", used_column, false); m_skeleton->addItemBool("pt_state", state_column, false); m_skeleton->addItemBool("pt_allocate", allocate_column, true); m_skeleton->addItemBool("pt_tags", tags_column, true); m_skeleton->addItemBool("pt_lvnames", lvnames_column, true); QCheckBox *const name_check = new QCheckBox(i18n("Volume name")); QCheckBox *const size_check = new QCheckBox(i18n("Size")); QCheckBox *const remaining_check = new QCheckBox(i18n("Remaining space")); QCheckBox *const used_check = new QCheckBox(i18n("Used space")); QCheckBox *const state_check = new QCheckBox(i18n("State")); QCheckBox *const allocate_check = new QCheckBox(i18n("Allocatable")); QCheckBox *const tags_check = new QCheckBox(i18n("Tags")); QCheckBox *const lvnames_check = new QCheckBox(i18n("Logical Volumes")); name_check->setObjectName("kcfg_pt_name"); size_check->setObjectName("kcfg_pt_size"); remaining_check->setObjectName("kcfg_pt_remaining"); used_check->setObjectName("kcfg_pt_used"); state_check->setObjectName("kcfg_pt_state"); allocate_check->setObjectName("kcfg_pt_allocate"); tags_check->setObjectName("kcfg_pt_tags"); lvnames_check->setObjectName("kcfg_pt_lvnames"); name_check->setToolTip(i18n("Show the path to the device, /dev/sda1 for example.")); size_check->setToolTip(i18n("Show the storage capacity in megabytes, gigabytes or terabytes.")); remaining_check->setToolTip(i18n("Show the remaining storage in megabytes, gigabytes or terabytes.")); used_check->setToolTip(i18n("Show the used storage in megabytes, gigabytes or terabytes.")); state_check->setToolTip(i18n("Show the state, either 'active' or 'inactive.'")); allocate_check->setToolTip(i18n("Shows whether or not the physical volume can have its extents allocated.")); tags_check->setToolTip(i18n("List any tags associated with the physical volume.")); lvnames_check->setToolTip(i18n("List any logical volumes associated with the physical volume.")); physical_layout->addWidget(name_check); physical_layout->addWidget(size_check); physical_layout->addWidget(remaining_check); physical_layout->addWidget(used_check); physical_layout->addWidget(state_check); physical_layout->addWidget(allocate_check); physical_layout->addWidget(tags_check); physical_layout->addWidget(lvnames_check); physical_layout->addStretch(); return physical_group; } QGroupBox *KvpmConfigDialog::logicalGroup() { QGroupBox *const logical_group = new QGroupBox(i18n("Logical volume tree")); QVBoxLayout *const logical_layout = new QVBoxLayout(); logical_group->setLayout(logical_layout); static bool volume_column; static bool size_column; static bool remaining_column; static bool type_column; static bool filesystem_column; static bool stripes_column; static bool stripesize_column; static bool snapmove_column; static bool state_column; static bool access_column; static bool tags_column; static bool mountpoints_column; m_skeleton->setCurrentGroup("VolumeTreeColumns"); // vt == volume tree m_skeleton->addItemBool("vt_volume", volume_column, true); m_skeleton->addItemBool("vt_size", size_column, true); m_skeleton->addItemBool("vt_remaining", remaining_column, true); m_skeleton->addItemBool("vt_type", type_column, true); m_skeleton->addItemBool("vt_filesystem", filesystem_column, false); m_skeleton->addItemBool("vt_stripes", stripes_column, false); m_skeleton->addItemBool("vt_stripesize", stripesize_column, false); m_skeleton->addItemBool("vt_snapmove", snapmove_column, true); m_skeleton->addItemBool("vt_state", state_column, true); m_skeleton->addItemBool("vt_access", access_column, false); m_skeleton->addItemBool("vt_tags", tags_column, true); m_skeleton->addItemBool("vt_mountpoints", mountpoints_column, false); QCheckBox *const volume_check = new QCheckBox(i18n("Volume name")); QCheckBox *const size_check = new QCheckBox(i18n("Size")); QCheckBox *const remaining_check = new QCheckBox(i18n("Remaining space")); QCheckBox *const type_check = new QCheckBox(i18n("Volume type")); QCheckBox *const filesystem_check = new QCheckBox(i18n("Filesystem type")); QCheckBox *const stripes_check = new QCheckBox(i18n("Stripe count")); QCheckBox *const stripesize_check = new QCheckBox(i18n("Stripe size")); QCheckBox *const snapmove_check = new QCheckBox(i18n("(\%)Data/Copy")); QCheckBox *const state_check = new QCheckBox(i18n("Volume state")); QCheckBox *const access_check = new QCheckBox(i18n("Volume access")); QCheckBox *const tags_check = new QCheckBox(i18n("Tags")); QCheckBox *const mountpoints_check = new QCheckBox(i18n("Mount point")); volume_check->setObjectName("kcfg_vt_volume"); size_check->setObjectName("kcfg_vt_size"); remaining_check->setObjectName("kcfg_vt_remaining"); type_check->setObjectName("kcfg_vt_type"); filesystem_check->setObjectName("kcfg_vt_filesystem"); stripes_check->setObjectName("kcfg_vt_stripes"); stripesize_check->setObjectName("kcfg_vt_stripesize"); snapmove_check->setObjectName("kcfg_vt_snapmove"); state_check->setObjectName("kcfg_vt_state"); access_check->setObjectName("kcfg_vt_access"); tags_check->setObjectName("kcfg_vt_tags"); mountpoints_check->setObjectName("kcfg_vt_mountpoints"); volume_check->setToolTip(i18n("Show the volume name.")); size_check->setToolTip(i18n("Show the storage capacity in megabytes, gigabytes or terabytes.")); remaining_check->setToolTip(i18n("Show the remaining storage in megabytes, gigabytes or terabytes.")); type_check->setToolTip(i18n("Show the type of volume, 'mirror' or 'linear,' for example.")); filesystem_check->setToolTip(i18n("Show the filesystem type on the volume, 'ext3' or 'swap,' for example.")); stripes_check->setToolTip(i18n("Show the number of stripes on the volume, if it is striped")); stripesize_check->setToolTip(i18n("Show the size of the stripes, if it is striped")); snapmove_check->setToolTip(i18n("For a mirror, show the percentage of the mirror synced. \n" "For a snapshot, show the percentage of the snapshot used. \n" "For a pvmove, show the percentage of the move completed.")); state_check->setToolTip(i18n("Show the state, 'active' or 'invalid,' for example.")); access_check->setToolTip(i18n("Show access, either read only or read and write.")); tags_check->setToolTip(i18n("List any tags associated with the volume")); mountpoints_check->setToolTip(i18n("Show the mount point if the partition has a mounted filesystem.")); logical_layout->addWidget(volume_check); logical_layout->addWidget(size_check); logical_layout->addWidget(remaining_check); logical_layout->addWidget(type_check); logical_layout->addWidget(filesystem_check); logical_layout->addWidget(stripes_check); logical_layout->addWidget(stripesize_check); logical_layout->addWidget(snapmove_check); logical_layout->addWidget(state_check); logical_layout->addWidget(access_check); logical_layout->addWidget(tags_check); logical_layout->addWidget(mountpoints_check); logical_layout->addStretch(); return logical_group; } QGroupBox *KvpmConfigDialog::allGroup() { QGroupBox *const all_group = new QGroupBox(i18n("All trees and tables")); QGroupBox *const percent_group = new QGroupBox(i18n("Remaining and used space")); QVBoxLayout *const all_layout = new QVBoxLayout(); QVBoxLayout *const percent_layout = new QVBoxLayout(); all_group->setLayout(all_layout); percent_group->setLayout(percent_layout); static bool show_percent; static bool show_total; static bool show_both; static int fs_warn_percent; static int pv_warn_percent; m_skeleton->setCurrentGroup("AllTreeColumns"); m_skeleton->addItemBool("show_percent", show_percent, false); m_skeleton->addItemBool("show_total", show_total, false); m_skeleton->addItemBool("show_both", show_both, true); m_skeleton->addItemInt("fs_warn", fs_warn_percent, 10); m_skeleton->addItemInt("pv_warn", pv_warn_percent, 0); QRadioButton *const percent_radio = new QRadioButton(i18n("Show percentage")); QRadioButton *const total_radio = new QRadioButton(i18n("Show total")); QRadioButton *const both_radio = new QRadioButton(i18n("Show both")); percent_radio->setObjectName("kcfg_show_percent"); total_radio->setObjectName("kcfg_show_total"); both_radio->setObjectName("kcfg_show_both"); percent_layout->addWidget(total_radio); percent_layout->addWidget(percent_radio); percent_layout->addWidget(both_radio); percent_layout->addWidget(new KSeparator(Qt::Horizontal)); QHBoxLayout *const warn_layout = new QHBoxLayout; QHBoxLayout *const fs_warn_layout = new QHBoxLayout; QHBoxLayout *const pv_warn_layout = new QHBoxLayout; warn_layout->addWidget(new QLabel(i18n("Show warning icon"))); QLabel *const warn_icon = new QLabel; warn_icon->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(16, 16)); warn_layout->addWidget(warn_icon); percent_layout->addLayout(warn_layout); percent_layout->addWidget(new QLabel(i18n("when space falls to or below:"))); QSpinBox *const fs_warn_spin = new QSpinBox; fs_warn_spin->setObjectName("kcfg_fs_warn"); fs_warn_spin->setRange(0, 99); fs_warn_spin->setSingleStep(1); fs_warn_spin->setPrefix("% "); fs_warn_spin->setSpecialValueText(i18n("Never")); fs_warn_layout->addWidget(fs_warn_spin); QLabel *const fs_warn_label = new QLabel(i18n("on a filesystem")); fs_warn_label->setBuddy(fs_warn_spin); fs_warn_layout->addWidget(fs_warn_label); fs_warn_layout->addStretch(); QSpinBox *const pv_warn_spin = new QSpinBox; pv_warn_spin->setObjectName("kcfg_pv_warn"); pv_warn_spin->setRange(0, 99); pv_warn_spin->setSingleStep(1); pv_warn_spin->setPrefix("% "); pv_warn_spin->setSpecialValueText(i18n("Never")); pv_warn_layout->addWidget(pv_warn_spin); QLabel *const pv_warn_label = new QLabel(i18n("on a physical volume")); pv_warn_label->setBuddy(pv_warn_spin); pv_warn_layout->addWidget(pv_warn_label); pv_warn_layout->addStretch(); percent_layout->addLayout(fs_warn_layout); percent_layout->addLayout(pv_warn_layout); all_layout->addWidget(percent_group); all_layout->addStretch(); return all_group; } QGroupBox *KvpmConfigDialog::devicePropertiesGroup() { QGroupBox *const properties = new QGroupBox(i18n("Device Properties Panel")); QVBoxLayout *const layout = new QVBoxLayout(); static bool mountpoint; static bool fsuuid; static bool fslabel; m_skeleton->setCurrentGroup("DeviceProperties"); // dp == device properties m_skeleton->addItemBool("dp_mount", mountpoint, true); m_skeleton->addItemBool("dp_fsuuid", fsuuid, false); m_skeleton->addItemBool("dp_fslabel", fslabel, false); QCheckBox *const mp_check = new QCheckBox(i18n("Mount points")); mp_check->setToolTip(i18n("Show the filesystem mount points for the device")); mp_check->setObjectName("kcfg_dp_mount"); QCheckBox *const fsuuid_check = new QCheckBox(i18n("Filesystem uuid")); fsuuid_check->setToolTip(i18n("Show the filesytem UUID")); fsuuid_check->setObjectName("kcfg_dp_fsuuid"); QCheckBox *const fslabel_check = new QCheckBox(i18n("Filesystem label")); fslabel_check->setToolTip(i18n("Show the filesystem label")); fslabel_check->setObjectName("kcfg_dp_fslabel"); layout->addWidget(mp_check); layout->addWidget(fsuuid_check); layout->addWidget(fslabel_check); layout->addStretch(); properties->setLayout(layout); return properties; } QGroupBox *KvpmConfigDialog::pvPropertiesGroup() { QGroupBox *const properties = new QGroupBox(i18n("Physical Volume Properties Panel")); QVBoxLayout *const layout = new QVBoxLayout(); static bool mda; static bool pvuuid; m_skeleton->setCurrentGroup("PhysicalVolumeProperties"); m_skeleton->addItemBool("pp_mda", mda, true); m_skeleton->addItemBool("pp_uuid", pvuuid, false); QCheckBox *const mda_check = new QCheckBox(i18n("Metadata areas")); mda_check->setToolTip(i18n("Show information about physical volume metadata")); mda_check->setObjectName("kcfg_pp_mda"); QCheckBox *const pvuuid_check = new QCheckBox(i18n("Physical volume uuid")); pvuuid_check->setToolTip(i18n("Show the physical volume UUID")); pvuuid_check->setObjectName("kcfg_pp_uuid"); layout->addWidget(mda_check); layout->addWidget(pvuuid_check); layout->addStretch(); properties->setLayout(layout); return properties; } QGroupBox *KvpmConfigDialog::lvPropertiesGroup() { QGroupBox *const properties = new QGroupBox(i18n("Logical Volume Properties Panel")); QVBoxLayout *const layout = new QVBoxLayout(); static bool mp; static bool fsuuid; static bool fslabel; static bool lvuuid; m_skeleton->setCurrentGroup("LogicalVolumeProperties"); // lp == logical volume properties m_skeleton->addItemBool("lp_mount", mp, true); m_skeleton->addItemBool("lp_fsuuid", fsuuid, false); m_skeleton->addItemBool("lp_fslabel", fslabel, false); m_skeleton->addItemBool("lp_uuid", lvuuid, false); QCheckBox *const mp_check = new QCheckBox(i18n("Mount points")); mp_check->setToolTip(i18n("Show the filesystem mount points for the device")); mp_check->setObjectName("kcfg_lp_mount"); QCheckBox *const fsuuid_check = new QCheckBox(i18n("Filesystem uuid")); fsuuid_check->setToolTip(i18n("Show the filesytem UUID")); fsuuid_check->setObjectName("kcfg_lp_fsuuid"); QCheckBox *const fslabel_check = new QCheckBox(i18n("Filesystem label")); fslabel_check->setToolTip(i18n("Show the filesystem label")); fslabel_check->setObjectName("kcfg_lp_fslabel"); QCheckBox *const lvuuid_check = new QCheckBox(i18n("Logical volume uuid")); lvuuid_check->setToolTip(i18n("Show the logical volume UUID")); lvuuid_check->setObjectName("kcfg_lp_uuid"); layout->addWidget(mp_check); layout->addWidget(fsuuid_check); layout->addWidget(fslabel_check); layout->addWidget(lvuuid_check); properties->setLayout(layout); layout->addStretch(); return properties; } QWidget *KvpmConfigDialog::colorsPage() { QGroupBox *const page = new QGroupBox(i18n("Volume and Partition Colors")); QVBoxLayout *const layout = new QVBoxLayout(); QHBoxLayout *const message_layout = new QHBoxLayout(); QLabel *const message = new QLabel(i18n("The graphical display of volumes and partitions can use " "color to show additional information. They can be displayed " "by type or by the type of filesystem on them")); message->setWordWrap(true); message_layout->addSpacing(50); message_layout->addWidget(message); layout->addLayout(message_layout); message_layout->addSpacing(50); layout->addSpacing(10); static int type_combo_index; m_skeleton->setCurrentGroup("General"); m_skeleton->addItemInt("type_combo", type_combo_index, 0); QHBoxLayout *const combo_layout = new QHBoxLayout(); QComboBox *const combo = new QComboBox(); combo->setObjectName("kcfg_type_combo"); combo->addItem(i18n("Use color graphics by volume or partition type")); combo->addItem(i18n("Use color graphics by filesystem type")); combo_layout->addStretch(); combo_layout->addWidget(combo); combo_layout->addStretch(); layout->addLayout(combo_layout); layout->addSpacing(10); m_color_stack = new QStackedWidget(); m_color_stack->addWidget(typeColors()); m_color_stack->addWidget(fsColors()); QHBoxLayout *const mid_layout = new QHBoxLayout(); mid_layout->addWidget(m_color_stack); mid_layout->addWidget(otherColors()); showOtherButtons(combo->currentIndex()); layout->addLayout(mid_layout); page->setLayout(layout); connect(combo, SIGNAL(currentIndexChanged(int)), m_color_stack, SLOT(setCurrentIndex(int))); connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(showOtherButtons(int))); return page; } QWidget *KvpmConfigDialog::fsColors() { static QColor ext2_color; static QColor ext3_color; static QColor ext4_color; static QColor btrfs_color; static QColor reiser_color; static QColor reiser4_color; static QColor msdos_color; static QColor jfs_color; static QColor xfs_color; static QColor hfs_color; static QColor ntfs_color; static QColor swap_color; static QColor physvol_color; static QColor none_color; m_skeleton->setCurrentGroup("FilesystemColors"); m_skeleton->addItemColor("ext2", ext2_color, Qt::blue); m_skeleton->addItemColor("ext3", ext3_color, Qt::darkBlue); m_skeleton->addItemColor("ext4", ext4_color, Qt::cyan); m_skeleton->addItemColor("btrfs", btrfs_color, Qt::yellow); m_skeleton->addItemColor("reiser", reiser_color, Qt::red); m_skeleton->addItemColor("reiser4", reiser4_color, Qt::darkRed); m_skeleton->addItemColor("msdos", msdos_color, Qt::darkYellow); m_skeleton->addItemColor("jfs", jfs_color, Qt::magenta); m_skeleton->addItemColor("xfs", xfs_color, Qt::darkGreen); m_skeleton->addItemColor("hfs", hfs_color, Qt::darkMagenta); m_skeleton->addItemColor("ntfs", ntfs_color, Qt::darkGray); m_skeleton->addItemColor("none", none_color, Qt::black); m_skeleton->addItemColor("swap", swap_color, Qt::lightGray); m_skeleton->addItemColor("physvol", physvol_color, Qt::darkCyan); QWidget *const fscolors = new QWidget; QGridLayout *const layout = new QGridLayout(); fscolors->setLayout(layout); KSeparator *const left_separator = new KSeparator(Qt::Vertical); KSeparator *const right_separator = new KSeparator(Qt::Vertical); layout->addWidget(left_separator, 1, 4, 5, 1); layout->addWidget(right_separator, 1, 9, 5, 1); QLabel *const ext2_label = new QLabel("ext2"); layout->addWidget(ext2_label, 1, 1, Qt::AlignRight); KColorButton *const ext2_button = new KColorButton(); ext2_button->setObjectName("kcfg_ext2"); layout->addWidget(ext2_button, 1, 2, Qt::AlignLeft); ext2_label->setBuddy(ext2_button); QLabel *const ext3_label = new QLabel("ext3"); layout->addWidget(ext3_label, 2, 1, Qt::AlignRight); KColorButton *const ext3_button = new KColorButton(); ext3_button->setObjectName("kcfg_ext3"); layout->addWidget(ext3_button, 2, 2, Qt::AlignLeft); ext3_label->setBuddy(ext3_button); QLabel *const ext4_label = new QLabel("ext4"); layout->addWidget(ext4_label, 3, 1, Qt::AlignRight); KColorButton *const ext4_button = new KColorButton(); ext4_button->setObjectName("kcfg_ext4"); layout->addWidget(ext4_button, 3, 2, Qt::AlignLeft); ext4_label->setBuddy(ext4_button); QLabel *const btrfs_label = new QLabel("btrfs"); layout->addWidget(btrfs_label, 4, 1, Qt::AlignRight); KColorButton *const btrfs_button = new KColorButton(); btrfs_button->setObjectName("kcfg_btrfs"); layout->addWidget(btrfs_button, 4, 2, Qt::AlignLeft); btrfs_label->setBuddy(btrfs_button); QLabel *const reiser_label = new QLabel("reiser"); layout->addWidget(reiser_label, 1, 6, Qt::AlignRight); KColorButton *const reiser_button = new KColorButton(); reiser_button->setObjectName("kcfg_reiser"); layout->addWidget(reiser_button, 1, 7, Qt::AlignLeft); reiser_label->setBuddy(reiser_button); QLabel *const reiser4_label = new QLabel("reiser4"); layout->addWidget(reiser4_label, 2, 6, Qt::AlignRight); KColorButton *const reiser4_button = new KColorButton(); reiser4_button->setObjectName("kcfg_reiser4"); layout->addWidget(reiser4_button, 2, 7, Qt::AlignLeft); reiser4_label->setBuddy(reiser4_button); QLabel *const msdos_label = new QLabel("ms-dos"); layout->addWidget(msdos_label, 3, 6, Qt::AlignRight); KColorButton *const msdos_button = new KColorButton(); msdos_button->setObjectName("kcfg_msdos"); layout->addWidget(msdos_button, 3, 7, Qt::AlignLeft); msdos_label->setBuddy(msdos_button); QLabel *const jfs_label = new QLabel("jfs"); layout->addWidget(jfs_label, 1, 11, Qt::AlignRight); KColorButton *const jfs_button = new KColorButton(); jfs_button->setObjectName("kcfg_jfs"); layout->addWidget(jfs_button, 1, 12, Qt::AlignLeft); jfs_label->setBuddy(jfs_button); QLabel *const xfs_label = new QLabel("xfs"); layout->addWidget(xfs_label, 2, 11, Qt::AlignRight); KColorButton *const xfs_button = new KColorButton(); xfs_button->setObjectName("kcfg_xfs"); layout->addWidget(xfs_button, 2, 12, Qt::AlignLeft); xfs_label->setBuddy(xfs_button); QLabel *const swap_label = new QLabel("linux \nswap"); layout->addWidget(swap_label, 3, 11, Qt::AlignRight); KColorButton *const swap_button = new KColorButton(); swap_button->setObjectName("kcfg_swap"); layout->addWidget(swap_button, 3, 12, Qt::AlignLeft); swap_label->setBuddy(swap_button); QLabel *const none_label = new QLabel("unknown"); layout->addWidget(none_label, 4, 6, Qt::AlignRight); KColorButton *const none_button = new KColorButton(); none_button->setObjectName("kcfg_none"); layout->addWidget(none_button, 4, 7, Qt::AlignLeft); none_label->setBuddy(none_button); QLabel *const hfs_label = new QLabel("hfs"); layout->addWidget(hfs_label, 4, 11, Qt::AlignRight); KColorButton *const hfs_button = new KColorButton(); hfs_button->setObjectName("kcfg_hfs"); layout->addWidget(hfs_button, 4, 12, Qt::AlignLeft); hfs_label->setBuddy(hfs_button); QLabel *const ntfs_label = new QLabel("ntfs"); layout->addWidget(ntfs_label, 5, 11, Qt::AlignRight); KColorButton *const ntfs_button = new KColorButton(); ntfs_button->setObjectName("kcfg_ntfs"); layout->addWidget(ntfs_button, 5, 12, Qt::AlignLeft); ntfs_label->setBuddy(ntfs_button); QLabel *const physical_label = new QLabel("physical \nvolumes"); layout->addWidget(physical_label, 5, 1, Qt::AlignRight); KColorButton *const physical_button = new KColorButton(); physical_button->setObjectName("kcfg_physvol"); layout->addWidget(physical_button, 5, 2, Qt::AlignLeft); physical_label->setBuddy(physical_button); return fscolors; } QWidget *KvpmConfigDialog::typeColors() { QWidget *const type = new QWidget; static QColor pvmove_color; static QColor mirror_color; static QColor raid1_color; static QColor raid456_color; static QColor cowsnap_color; static QColor invalid_color; static QColor other_color; static QColor linear_color; static QColor thinsnap_color; static QColor thinvol_color; static QColor inactive_color; m_skeleton->setCurrentGroup("VolumeTypeColors"); m_skeleton->addItemColor("mirror", mirror_color, Qt::darkBlue); // lvm type mirror m_skeleton->addItemColor("raid1", raid1_color, Qt::blue); m_skeleton->addItemColor("raid456", raid456_color, Qt::cyan); m_skeleton->addItemColor("thinvol", thinvol_color, Qt::lightGray); m_skeleton->addItemColor("invalid", invalid_color, Qt::red); m_skeleton->addItemColor("thinsnap", thinsnap_color, Qt::darkRed); m_skeleton->addItemColor("cowsnap", cowsnap_color, Qt::darkYellow); m_skeleton->addItemColor("linear", linear_color, Qt::darkCyan); m_skeleton->addItemColor("pvmove", pvmove_color, Qt::magenta); m_skeleton->addItemColor("other", other_color, Qt::yellow); m_skeleton->addItemColor("inactive", inactive_color, Qt::black); QGridLayout *const layout = new QGridLayout(); type->setLayout(layout); KSeparator *const left_separator = new KSeparator(Qt::Vertical); KSeparator *const right_separator = new KSeparator(Qt::Vertical); layout->addWidget(left_separator, 1, 4, 4, 1); layout->addWidget(right_separator, 1, 9, 4, 1); QLabel *const linear_label = new QLabel("linear"); layout->addWidget(linear_label, 1, 1, Qt::AlignRight); KColorButton *const linear_button = new KColorButton(); linear_button->setObjectName("kcfg_linear"); layout->addWidget(linear_button, 1, 2, Qt::AlignLeft); linear_label->setBuddy(linear_button); QLabel *const raid1_label = new QLabel("RAID 1"); layout->addWidget(raid1_label, 2, 1, Qt::AlignRight); KColorButton *const raid1_button = new KColorButton(); raid1_button->setObjectName("kcfg_raid1"); layout->addWidget(raid1_button, 2, 2, Qt::AlignLeft); raid1_label->setBuddy(raid1_button); QLabel *const raid456_label = new QLabel("RAID 4/5/6"); layout->addWidget(raid456_label, 3, 1, Qt::AlignRight); KColorButton *const raid456_button = new KColorButton(); raid456_button->setObjectName("kcfg_raid456"); layout->addWidget(raid456_button, 3, 2, Qt::AlignLeft); raid456_label->setBuddy(raid456_button); QLabel *const cowsnap_label = new QLabel("snapshot"); layout->addWidget(cowsnap_label, 4, 1, Qt::AlignRight); KColorButton *const cowsnap_button = new KColorButton(); cowsnap_button->setObjectName("kcfg_cowsnap"); layout->addWidget(cowsnap_button, 4, 2, Qt::AlignLeft); cowsnap_label->setBuddy(cowsnap_button); QLabel *const invalid_label = new QLabel("invalid snap"); layout->addWidget(invalid_label, 5, 1, Qt::AlignRight); KColorButton *const invalid_button = new KColorButton(); invalid_button->setObjectName("kcfg_invalid"); layout->addWidget(invalid_button, 5, 2, Qt::AlignLeft); invalid_label->setBuddy(invalid_button); QLabel *const mirror_label = new QLabel("lvm mirror"); layout->addWidget(mirror_label, 6, 1, Qt::AlignRight); KColorButton *const mirror_button = new KColorButton(); mirror_button->setObjectName("kcfg_mirror"); layout->addWidget(mirror_button, 6, 2, Qt::AlignLeft); mirror_label->setBuddy(mirror_button); QLabel *const other_label = new QLabel("other"); layout->addWidget(other_label, 1, 6, Qt::AlignRight); KColorButton *const other_button = new KColorButton(); other_button->setObjectName("kcfg_other"); layout->addWidget(other_button, 1, 7, Qt::AlignLeft); other_label->setBuddy(other_button); QLabel *const pvmove_label = new QLabel("pvmove"); layout->addWidget(pvmove_label, 2, 6, Qt::AlignRight); KColorButton *const pvmove_button = new KColorButton(); pvmove_button->setObjectName("kcfg_pvmove"); layout->addWidget(pvmove_button, 2, 7, Qt::AlignLeft); pvmove_label->setBuddy(pvmove_button); QLabel *const inactive_label = new QLabel("inactive"); layout->addWidget(inactive_label, 3, 6, Qt::AlignRight); KColorButton *const inactive_button = new KColorButton(); inactive_button->setObjectName("kcfg_inactive"); layout->addWidget(inactive_button, 3, 7, Qt::AlignLeft); inactive_label->setBuddy(inactive_button); QLabel *const thinvol_label = new QLabel("thin volume"); layout->addWidget(thinvol_label, 4, 6, Qt::AlignRight); KColorButton *const thinvol_button = new KColorButton(); thinvol_button->setObjectName("kcfg_thinvol"); layout->addWidget(thinvol_button, 4, 7, Qt::AlignLeft); thinvol_label->setBuddy(thinvol_button); QLabel *const thinsnap_label = new QLabel("thin snapshot"); layout->addWidget(thinsnap_label, 5, 6, Qt::AlignRight); KColorButton *const thinsnap_button = new KColorButton(); thinsnap_button->setObjectName("kcfg_thinsnap"); layout->addWidget(thinsnap_button, 5, 7, Qt::AlignLeft); thinsnap_label->setBuddy(thinsnap_button); return type; } QWidget *KvpmConfigDialog::otherColors() { QWidget *const type = new QWidget; static QColor free_color; m_skeleton->setCurrentGroup("VolumeTypeColors"); m_skeleton->addItemColor("free", free_color, Qt::green); static QColor primary_color; static QColor logical_color; static QColor extended_color; m_skeleton->setCurrentGroup("PartitionTypeColors"); m_skeleton->addItemColor("primary", primary_color, Qt::cyan); m_skeleton->addItemColor("logical", logical_color, Qt::blue); m_skeleton->addItemColor("extended", extended_color, Qt::darkGreen); QGridLayout *const layout = new QGridLayout(); type->setLayout(layout); QLabel *const free_label = new QLabel("unused space"); layout->addWidget(free_label, 1, 1, Qt::AlignRight); KColorButton *const free_button = new KColorButton(); free_button->setToolTip(i18n("Any unused space in a volume group or unpartitioned space on a device")); free_button->setObjectName("kcfg_free"); layout->addWidget(free_button, 1, 2, Qt::AlignLeft); free_label->setBuddy(free_button); QLabel *const extended_label = new QLabel("extended space"); layout->addWidget(extended_label, 2, 1, Qt::AlignRight); KColorButton *const extended_button = new KColorButton(); extended_button->setObjectName("kcfg_extended"); extended_button->setToolTip(i18n("Any unpartitioned space inside an extended partition")); layout->addWidget(extended_button, 2, 2, Qt::AlignLeft); extended_label->setBuddy(extended_button); m_primary_label = new QLabel("primary partition"); layout->addWidget(m_primary_label, 3, 1, Qt::AlignRight); m_primary_button = new KColorButton(); m_primary_button->setObjectName("kcfg_primary"); m_primary_button->setToolTip(i18n("An ordinary partition")); layout->addWidget(m_primary_button, 3, 2, Qt::AlignLeft); m_primary_label->setBuddy(m_primary_button); m_logical_label = new QLabel("logical partition"); layout->addWidget(m_logical_label, 4, 1, Qt::AlignRight); m_logical_button = new KColorButton(); m_logical_button->setObjectName("kcfg_logical"); m_logical_button->setToolTip(i18n("A partition inside an extended partition")); layout->addWidget(m_logical_button, 4, 2, Qt::AlignLeft); m_logical_label->setBuddy(m_logical_button); layout->setRowMinimumHeight(1, 40); layout->setRowMinimumHeight(2, 40); layout->setRowMinimumHeight(3, 40); layout->setRowMinimumHeight(4, 40); layout->setRowStretch(5, 1); return type; } void KvpmConfigDialog::showOtherButtons(int index) { if (index == 1) { m_primary_button->hide(); m_logical_button->hide(); m_primary_label->hide(); m_logical_label->hide(); } else { m_primary_button->show(); m_logical_button->show(); m_primary_label->show(); m_logical_label->show(); } } kvpm-0.9.10/kvpm/volgroup.h0000644000175000017500000001063112770324126016012 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VOLGROUP_H #define VOLGROUP_H #include #include #include "allocationpolicy.h" #include "logvol.h" #include "misc.h" class LogVol; class MountTables; class PhysVol; class VolGroup { MountTables *m_tables; QList m_member_lvs; // lvs that belong to this group QList m_member_pvs; // pvs that belong to this group long long m_extent_size; int m_lv_max; // maximum number of logical volumes, unlimited == 0 int m_pv_max; long long m_mda_count; // number of metadata areas in tge group long long m_mda_used; // number of usable mda's long long m_size; // total size of volume group in bytes long long m_free; // free space in bytes long long m_extents; long long m_free_extents; // free extents are not always useable long long m_allocatable_extents; // extents on some physical volumes // may not be allocateable QString m_vg_name; // this volume group name QString m_uuid; QString m_lvm_format; // lvm1 or lvm2 AllocationPolicy m_policy; QStringList m_lv_names_all; // names of all lvs and sub lvs in group, including metadata bool m_active; // if any lv is active the group is active bool m_writable; bool m_resizable; bool m_clustered; bool m_exported; bool m_partial; // some physical volumes may be missing bool m_open_failed; // lvm couldn't open the vg, clustering without a working clvmd can do that lv_t findOrphan(QList &childList); void processLogicalVolumes(vg_t lvmVG); void processPhysicalVolumes(vg_t lvmVG); void setActivePhysicalVolumes(); void setLastUsedExtent(); public: VolGroup(lvm_t lvm, const char *vgname, MountTables *const tables); ~VolGroup(); void rescan(lvm_t lvm); LvList getLogicalVolumes() const; // *TOP LEVEL ONLY* snapcontainers returned not snaps and origin LvList getLogicalVolumesFlat() const; // un-nest the volumes, snapshots and mirror legs QList getPhysicalVolumes() const { return m_member_pvs; } QStringList getLvNamesAll() const { return m_lv_names_all; } // unsorted list of all lvs and sub lvs LogVol *getLvByName(QString shortName) const; // lv name without the vg name and "/" -- skips snap containers LogVol *getLvByUuid(QString uuid) const; // also skips snap containers PhysVol *getPvByName(QString name) const; // /dev/something long long getExtents() const { return m_extents; } long long getFreeExtents() const { return m_free_extents; } long long getAllocatableExtents() const { return m_allocatable_extents; } long long getAllocatableSpace() const { return m_allocatable_extents * (long long)m_extent_size; } long long getExtentSize() const { return m_extent_size; } long long getSize() const { return m_extents * m_extent_size; } long long getFreeSpace() const { return m_free_extents * m_extent_size; } long long getUsedSpace() const { return (m_extents - m_free_extents) * m_extent_size; } int getLvCount() const { return m_member_lvs.size(); } int getLvMax() const { return m_lv_max; } int getPvCount() const { return m_member_pvs.size(); } int getPvMax() const { return m_pv_max; } int getMdaCount() const { return m_mda_count; } int getMdaUsed() const { return m_mda_used; } QString getName() const { return m_vg_name; } QString getUuid() const { return m_uuid; } QString getFormat() const { return m_lvm_format; } AllocationPolicy getPolicy() const { return m_policy; } QStringList getLvNames() const; bool isWritable() const { return m_writable; } bool isResizable() const { return m_resizable; } bool isClustered() const { return m_clustered; } bool isPartial() const { return m_partial; } bool isExported() const { return m_exported; } bool isActive() const { return m_active; } bool openFailed() const { return m_open_failed; } }; #endif kvpm-0.9.10/kvpm/lvmconfig.h0000644000175000017500000000224712770324126016125 0ustar benscottbenscott/* * * * Copyright (C) 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVMCONFIG_H #define LVMCONFIG_H #include class LvmConfig { static long long m_mirror_region_size; static QString m_mirror_segtype_default; static bool m_mirror_logs_require_separate_pvs; static bool m_thin_pool_metadata_require_separate_pvs; static bool m_maximise_cling; QStringList getConfig(); void setGlobal(const QStringList &variables); void setAllocation(const QStringList &variables); void setActivation(const QStringList &variables); public: LvmConfig(); ~LvmConfig(); void initialize(); static long long getMirrorRegionSize(); static QString getMirrorSegtypeDefault(); static bool getMirrorLogsRequireSeparatePvs(); static bool getThinPoolMetadataRequireSeparatePvs(); static bool getMaximiseCling(); }; #endif kvpm-0.9.10/kvpm/changemirror.h0000644000175000017500000000526012770324126016617 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef CHANGEMIRROR_H #define CHANGEMIRROR_H #include #include #include "kvpmdialog.h" class QComboBox; class QGroupBox; class QRadioButton; class QSpinBox; class QStackedWidget; class QTabWidget; class LogVol; class NoMungeCheck; class NoMungeRadioButton; class PvGroupBox; class PvSpace; class ChangeMirrorDialog : public KvpmDialog { Q_OBJECT QTabWidget *m_tab_widget = nullptr; QSpinBox *m_add_mirrors_spin = nullptr; QSpinBox *m_stripe_spin = nullptr; QStackedWidget *m_error_stack = nullptr; QStackedWidget *m_log_stack = nullptr; PvGroupBox *m_pv_box = nullptr; QGroupBox *m_stripe_box = nullptr; QGroupBox *m_log_box = nullptr; QWidget *m_log_widget = nullptr; QComboBox *m_stripe_size_combo = nullptr; QComboBox *m_type_combo = nullptr; QStringList m_log_pvs; QStringList m_image_pvs; QList> m_space_list; QList m_mirror_log_checks; bool m_change_log; // true if we just changing the logging of an existing mirror LogVol *m_lv; // The volume we are adding a mirror leg to. QRadioButton *m_core_log_button, *m_mirrored_log_button, *m_disk_log_button; // these are the pv names of the two mirror logs if we are changing the log // count on a mirrored log NoMungeRadioButton *m_log_one; NoMungeRadioButton *m_log_two; QWidget *buildGeneralTab(const bool isRaidMirror, const bool isLvmMirror); QWidget *buildPhysicalTab(const bool isRaidMirror); QWidget *buildLogWidget(); QStringList getLogPvs(); QStringList getImagePvs(); QList> getPvSpaceList(); bool pvHasLog(QString const pv); bool pvHasImage(QString const pv); bool validateStripeSpin(); void setLogRadioButtons(); int getNewLogCount(); long long getNewLogSize(); bool getAvailableByteList(QList &byte_list, int &unhandledLogs, const int stripes); public: ChangeMirrorDialog(LogVol *const mirrorVolume, const bool changeLog, QWidget *parent = nullptr); QStringList arguments(); private slots: void resetOkButton(); void commit(); void enableTypeOptions(int index); void enableLogWidget(); }; #endif kvpm-0.9.10/kvpm/vgsplit.h0000644000175000017500000000423312770324126015626 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011, 2012, 2013, 2014 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGSPLIT_H #define VGSPLIT_H #include #include #include "kvpmdialog.h" #include "logvol.h" class QLineEdit; class QListWidget; class QRegExpValidator; class LogVol; class VolGroup; class VGSplitDialog : public KvpmDialog { Q_OBJECT QListWidget *m_left_lv_list; QListWidget *m_right_lv_list; QListWidget *m_left_pv_list; QListWidget *m_right_pv_list; QPushButton *m_lv_add; QPushButton *m_lv_remove; QPushButton *m_pv_add; QPushButton *m_pv_remove; QLineEdit *m_new_vg_name; QRegExpValidator *m_validator; VolGroup *m_vg; LvList getFullLvList(); void deactivate(); // active lvs must be deactivated before moving QWidget *buildLvLists(const QStringList mobileLvNames, const QStringList fixedLvNames); QWidget *buildPvLists(const QStringList mobilePvNames, const QStringList fixedPvNames); QStringList getPvs(LogVol *const lv); void volumeMobility(QStringList &mobileLvNames, QStringList &fixedLvNames, QStringList &mobilePvNames, QStringList &fixedPvNames); void pvState(QStringList &open, QStringList &closed); void movesWithVolume(const bool isLV, const QString name, QStringList &movingPvNames, QStringList &movingLvNames); void moveNames(const bool isLvMove, QListWidget *const lvSource, QListWidget *const lvTarget, QListWidget *const pvSource, QListWidget *const pvTarget); public: explicit VGSplitDialog(VolGroup *volumeGroup, QWidget *parent = nullptr); private slots: void addLvList(); void addPvList(); void commit(); void enableLvArrows(); void enablePvArrows(); void removePvList(); void removeLvList(); void validateOK(); }; #endif kvpm-0.9.10/kvpm/vgactions.cpp0000644000175000017500000001750012770324126016467 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgactions.h" #include "kvpmdialog.h" #include "physvol.h" #include "topwindow.h" #include "vgreduce.h" #include "vgchange.h" #include "vgcreate.h" #include "vgextend.h" #include "vgremove.h" #include "vgremovemissing.h" #include "vgrename.h" #include "vgexport.h" #include "vgimport.h" #include "vgsplit.h" #include "vgmerge.h" #include "volgroup.h" #include #include VGActions::VGActions(QWidget *parent) : KActionCollection(parent) { QAction *const remove = addAction("vgremove"); QAction *const reduce = addAction("vgreduce"); QAction *const extend = addAction("vgextend"); QAction *const rename = addAction("vgrename"); QAction *const merge = addAction("vgmerge"); QAction *const split = addAction("vgsplit"); QAction *const change = addAction("vgchange"); QAction *const create = addAction("vgcreate"); QAction *const vgimport = addAction("vgimport"); QAction *const vgexport = addAction("vgexport"); QAction *const remove_missing = addAction("vgremovemissing"); remove->setText(i18n("Delete Volume Group...")); reduce->setText(i18n("Reduce Volume Group...")); extend->setText(i18n("Extend Volume Group...")); rename->setText(i18n("Rename Volume Group...")); merge->setText(i18n("Merge Volume Group...")); split->setText(i18n("Split Volume Group...")); change->setText(i18n("Change Volume Group Attributes...")); create->setText(i18n("Create Volume Group...")); vgimport->setText(i18n("Import Volume Group...")); vgexport->setText(i18n("Export Volume Group...")); remove_missing->setText(i18n("Remove Missing Physcial Volumes...")); remove->setToolTip(i18n("Delete Volume Group")); reduce->setToolTip(i18n("Reduce Volume Group")); extend->setToolTip(i18n("Extend Volume Group")); rename->setToolTip(i18n("Rename Volume Group")); merge->setToolTip(i18n("Merge Volume Group")); split->setToolTip(i18n("Split Volume Group")); change->setToolTip(i18n("Change Volume Group Attributes")); create->setToolTip(i18n("Create Volume Group")); vgimport->setToolTip(i18n("Import Volume Group")); vgexport->setToolTip(i18n("Export Volume Group")); remove_missing->setToolTip(i18n("Remove Missing Physcial Volumes")); remove->setIconText(i18n("Delete")); reduce->setIconText(i18n("Reduce")); extend->setIconText(i18n("Extend")); rename->setIconText(i18n("Rename")); merge->setIconText(i18n("Merge")); split->setIconText(i18n("Split")); change->setIconText(i18n("Change Attributes")); create->setIconText(i18n("Create")); vgimport->setIconText(i18n("Import")); vgexport->setIconText(i18n("Export")); remove_missing->setIconText(i18n("Remove Missing")); remove->setIcon(QIcon::fromTheme(QStringLiteral("cross"))); reduce->setIcon(QIcon::fromTheme(QStringLiteral("delete"))); extend->setIcon(QIcon::fromTheme(QStringLiteral("add"))); rename->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); merge->setIcon(QIcon::fromTheme(QStringLiteral("arrow_join"))); split->setIcon(QIcon::fromTheme(QStringLiteral("arrow_divide"))); change->setIcon(QIcon::fromTheme(QStringLiteral("wrench")) ); create->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); vgimport->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); vgexport->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); remove_missing->setIcon(QIcon::fromTheme(QStringLiteral("error_go"))); setVg(nullptr); connect(this, SIGNAL(actionTriggered(QAction *)), this, SLOT(callDialog(QAction *))); } void VGActions::setVg(VolGroup *vg) { m_vg = vg; QAction *const remove = action("vgremove"); QAction *const reduce = action("vgreduce"); QAction *const extend = action("vgextend"); QAction *const rename = action("vgrename"); QAction *const merge = action("vgmerge"); QAction *const split = action("vgsplit"); QAction *const change = action("vgchange"); QAction *const vgimport = action("vgimport"); QAction *const vgexport = action("vgexport"); QAction *const remove_missing = action("vgremovemissing"); // only enable group removal if the tab is // a volume group with no logical volumes if (vg && !vg->openFailed()) { bool has_active = false; auto lvs = vg->getLogicalVolumes(); for (auto lv : lvs) { if (lv->isActive()) { has_active = true; break; } } if (lvs.size() || vg->isPartial() || vg->isExported()) remove->setEnabled(false); else remove->setEnabled(true); remove_missing->setEnabled(vg->isPartial()); change->setEnabled(!vg->isExported()); if (vg->isExported()) { split->setEnabled(false); merge->setEnabled(false); vgimport->setEnabled(true); vgexport->setEnabled(false); reduce->setEnabled(false); extend->setEnabled(false); } else if (!vg->isPartial()) { vgimport->setEnabled(false); reduce->setEnabled(true); split->setEnabled(true); merge->setEnabled(true); extend->setEnabled(true); if (has_active) vgexport->setEnabled(false); else vgexport->setEnabled(true); } else { split->setEnabled(false); merge->setEnabled(false); vgimport->setEnabled(false); vgexport->setEnabled(false); reduce->setEnabled(false); extend->setEnabled(false); } rename->setEnabled(true); } else { reduce->setEnabled(false); rename->setEnabled(false); remove->setEnabled(false); remove_missing->setEnabled(false); change->setEnabled(false); vgimport->setEnabled(false); split->setEnabled(false); merge->setEnabled(false); vgexport->setEnabled(false); extend->setEnabled(false); } } void VGActions::callDialog(QAction *action) { const QString name = action->objectName(); if (name == "vgremove" || name == "vgimport" || name == "vgexport") { if (name == "vgremove") { if (remove_vg(m_vg)) g_top_window->reRun(); } else if (name == "vgimport") { if (import_vg(m_vg)) g_top_window->reRun(); } else if (name == "vgexport") { if (export_vg(m_vg)) g_top_window->reRun(); } } else { KvpmDialog *dialog = nullptr; if (name == "vgchange") dialog = new VGChangeDialog(m_vg); else if (name == "vgcreate") dialog = new VGCreateDialog(); else if (name == "vgreduce") dialog = new VGReduceDialog(m_vg); else if (name == "vgextend") dialog = new VGExtendDialog(m_vg); else if (name == "vgsplit") dialog = new VGSplitDialog(m_vg); else if (name == "vgmerge") dialog = new VGMergeDialog(m_vg); else if (name == "vgrename") dialog = new VGRenameDialog(m_vg); else if (name == "vgremovemissing") dialog = new VGRemoveMissingDialog(m_vg); if (dialog) { int result = dialog->run(); if (result == QDialog::Accepted || result == KDialog::Yes) g_top_window->reRun(); } } } kvpm-0.9.10/kvpm/storagepartition.h0000644000175000017500000000674012770324126017541 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef STORAGEPARTITION_H #define STORAGEPARTITION_H #include #include #include "mountentry.h" #include "mounttables.h" #include "storagebase.h" class PhysVol; class StoragePartition : public StorageBase { MountList m_mount_entries; QString m_fstab_mount_point; QStringList m_mount_points; PedPartition *m_ped_partition; QString m_name; QString m_partition_type; unsigned int m_ped_type; QString m_fs_type; QString m_fs_uuid; QString m_fs_label; QStringList m_flags; long long m_partition_size; // Partition size and first sector for *freespace* are aligned PedSector m_first_sector; // within the space to 1 MiB and may be different than libparted reports. PedSector m_last_sector; // Last sector is not aligned and will be the same as libarted PedSector m_true_first_sector; // unaligned first sector long long m_fs_size; long long m_fs_used; bool m_is_mounted; bool m_is_extended; bool m_is_empty; // empty extended partition bool m_is_mountable; bool m_is_normal; bool m_is_logical; bool m_is_freespace; bool m_is_logical_freespace; PedSector getAlignedStart(); PedSector getFreespaceEnd(); public: StoragePartition(PedPartition *const part, const int freespaceCount, const QList pvList, MountTables *const tables, const QStringList mdblock); ~StoragePartition(); QString getName() const { return m_name; } PedPartition *getPedPartition() const { return m_ped_partition; } QString getFilesystem() const { return m_fs_type; } QString getFilesystemUuid() const { return m_fs_uuid; } QString getFilesystemLabel() const { return m_fs_label; } QString getType() const { return m_partition_type; } unsigned int getPedType() const { return m_ped_type; } QString getFstabMountPoint() const { return m_fstab_mount_point; } QStringList getMountPoints() const { return m_mount_points; } MountList getMountEntries() const { return m_mount_entries; } QStringList getFlags() const { return m_flags; } long long getSize() const { return m_partition_size; } PedSector getFirstSector() const { return m_first_sector; } PedSector getTrueFirstSector() const { return m_true_first_sector; } PedSector getLastSector() const { return m_last_sector; } long long getFilesystemSize() const { return m_fs_size; } long long getFilesystemUsed() const { return m_fs_used; } long long getFilesystemRemaining() const { return m_fs_size - m_fs_used; } int getFilesystemPercentUsed() const ; bool isMounted() const { return m_is_mounted; } bool isEmptyExtended() const ; bool isExtended() const { return m_is_extended; } bool isMountable() const { return m_is_mountable; } bool isNormal() const { return m_is_normal; } bool isLogical() const { return m_is_logical; } bool isLogicalFreespace() const { return m_is_logical_freespace; } bool isFreespace() const { return m_is_freespace; } }; #endif kvpm-0.9.10/kvpm/partitiongraphic.h0000644000175000017500000000200212770324126017475 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PARTITIONGRAPHIC_H #define PARTITIONGRAPHIC_H #include #include class GraphicBody; class PartitionGraphic : public QGroupBox { Q_OBJECT bool m_use_si_units; GraphicBody *m_body; long long m_total_space; QLabel *m_preceding_label, *m_following_label, *m_change_label, *m_move_label; public: PartitionGraphic(const long long space, const bool isNewPart,QWidget *parent = 0); void update(long long size, long long offset, const long long move, const long long change); void update(const long long size, const long long offset); }; #endif kvpm-0.9.10/kvpm/tablecreate.h0000644000175000017500000000155712770324126016417 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2010, 2011, 2012, 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef TABLECREATE_H #define TABLECREATE_H #include class QRadioButton; class QString; bool create_table(const QString devicePath); class TableCreateDialog : public KDialog { Q_OBJECT QString m_device_path; QRadioButton *m_msdos_button, *m_gpt_button, *m_destroy_button; public: explicit TableCreateDialog(const QString devicePath, QWidget *parent = nullptr); private slots: void commitTable(); }; #endif kvpm-0.9.10/kvpm/vgremovemissing.h0000644000175000017500000000146212770324126017363 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGREMOVEMISSING_H #define VGREMOVEMISSING_H class QRadioButton; class VolGroup; #include "kvpmdialog.h" class VGRemoveMissingDialog : public KvpmDialog { Q_OBJECT VolGroup *m_vg = nullptr; QRadioButton *m_empty_button, *m_all_button; public: explicit VGRemoveMissingDialog(VolGroup *const group, QWidget *parent = nullptr); private slots: void commit(); }; #endif kvpm-0.9.10/kvpm/processprogress.h0000644000175000017500000000217112770324126017400 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PROCESSPROGRESS_H #define PROCESSPROGRESS_H #include #include #include class QProgressDialog; class QEventLoop; class ProcessProgress : public QObject { Q_OBJECT QEventLoop *m_loop; QStringList m_output_all, m_output_no_error; QProcess *m_process; QProgressDialog *m_progress_dialog; int m_exit_code; public: ProcessProgress(QStringList arguments, const bool allowCancel = false, QObject *parent = NULL); QStringList programOutput(); QStringList programOutputAll(); int exitCode(); private slots: void cleanup(const int code, const QProcess::ExitStatus status); void cancelProcess(); void readStandardOut(); void readStandardError(); }; #endif kvpm-0.9.10/kvpm/removemirrorleg.h0000644000175000017500000000075612770324126017364 0ustar benscottbenscott/* * * * Copyright (C) 2008 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef REMOVEMIRRORLEG_H #define REMOVEMIRRORLEG_H class LogVol; bool remove_mirror_leg(LogVol *mirrorLeg); #endif kvpm-0.9.10/kvpm/changemirror.cpp0000644000175000017500000006536712770324126017170 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "changemirror.h" #include "logvol.h" #include "lvmconfig.h" #include "misc.h" #include "physvol.h" #include "pvgroupbox.h" #include "processprogress.h" #include "volgroup.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ChangeMirrorDialog::ChangeMirrorDialog(LogVol *const mirrorVolume, const bool changeLog, QWidget *parent) : KvpmDialog(parent), m_change_log(changeLog), m_lv(mirrorVolume) { if (m_lv->getParentMirror()) m_lv = m_lv->getParentMirror(); else if (m_lv->getParentRaid()) m_lv = m_lv->getParentRaid(); QList children; const QString lv_name = m_lv->getName(); const bool is_raid = (m_lv->isMirror() && m_lv->isRaid()); const bool is_lvm = (m_lv->isMirror() && !m_lv->isRaid()); m_log_pvs = getLogPvs(); m_image_pvs = getImagePvs(); setCaption(i18n("Change Mirror")); if ((is_raid && !m_lv->isMirror()) || m_lv->isCowSnap()) { preventExec(); KMessageBox::sorry(nullptr, i18n("This type of volume can not be mirrored ")); } else if (is_raid && !m_lv->isSynced()) { preventExec(); KMessageBox::sorry(nullptr, i18n("RAID mirrors must be synced before adding new legs")); } else if (is_lvm && m_lv->isCowOrigin() && !m_change_log) { preventExec(); KMessageBox::sorry(nullptr, i18n("Non-RAID mirrors which are snapshot origins can not have new legs added")); } else { QWidget *const main_widget = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); QLabel *const lv_name_label = new QLabel(); if(m_change_log) lv_name_label->setText(i18n("Change mirror log: %1", m_lv->getName())); else if(is_lvm) lv_name_label->setText(i18n("Change LVM mirror: %1", m_lv->getName())); else if(is_raid) lv_name_label->setText(i18n("Change RAID 1 mirror: %1", m_lv->getName())); else lv_name_label->setText(i18n("Convert to a mirror: %1", m_lv->getName())); lv_name_label->setAlignment(Qt::AlignCenter); m_tab_widget = new QTabWidget(); layout->addWidget(lv_name_label); layout->addSpacing(10); layout->addWidget(m_tab_widget); main_widget->setLayout(layout); setMainWidget(main_widget); m_tab_widget->addTab(buildGeneralTab(is_raid, is_lvm), i18nc("Common user options", "General")); if (!(m_change_log && (m_lv->getLogCount() == 2))) { m_tab_widget->addTab(buildPhysicalTab(is_raid), i18n("Physical layout")); connect(m_pv_box, SIGNAL(stateChanged()), this, SLOT(resetOkButton())); connect(m_add_mirrors_spin, SIGNAL(valueChanged(int)), this, SLOT(resetOkButton())); connect(m_stripe_spin, SIGNAL(valueChanged(int)), this, SLOT(resetOkButton())); connect(m_stripe_box, SIGNAL(toggled(bool)), this, SLOT(resetOkButton())); } else { m_pv_box = nullptr; } setLogRadioButtons(); connect(m_disk_log_button, SIGNAL(toggled(bool)), this, SLOT(resetOkButton())); connect(m_core_log_button, SIGNAL(toggled(bool)), this, SLOT(resetOkButton())); connect(m_mirrored_log_button, SIGNAL(toggled(bool)), this, SLOT(resetOkButton())); enableTypeOptions(m_type_combo->currentIndex()); if (!m_change_log && (m_space_list.size() < 1)) { preventExec(); KMessageBox::sorry(nullptr, i18n("No physical volumes suitable for a new mirror image found")); } } } QWidget *ChangeMirrorDialog::buildGeneralTab(const bool isRaidMirror, const bool isLvmMirror) { QWidget *const general = new QWidget; QHBoxLayout *const general_layout = new QHBoxLayout; QVBoxLayout *const center_layout = new QVBoxLayout; const bool is_mirror = (isRaidMirror || isLvmMirror); m_type_combo = new QComboBox(); m_type_combo->addItem(i18n("Standard LVM")); m_type_combo->addItem(i18n("RAID 1")); m_add_mirrors_spin = new QSpinBox(this); m_add_mirrors_spin->setMaximum(10); m_add_mirrors_spin->setMinimum(1); m_add_mirrors_spin->setSingleStep(1); m_add_mirrors_spin->setValue(1); general_layout->addStretch(); general_layout->addLayout(center_layout); general_layout->addStretch(); QGroupBox *const add_mirror_box = new QGroupBox(); QLabel *const type_label = new QLabel(i18n("Mirror type: ")); QHBoxLayout *const type_layout = new QHBoxLayout; type_layout->addWidget(type_label); type_layout->addWidget(m_type_combo); QVBoxLayout *const add_mirror_box_layout = new QVBoxLayout; add_mirror_box_layout->addLayout(type_layout); add_mirror_box->setLayout(add_mirror_box_layout); center_layout->addStretch(); center_layout->addWidget(add_mirror_box); QLabel *const existing_label = new QLabel(i18n("Existing mirror legs: %1", m_lv->getMirrorCount())); add_mirror_box_layout->addWidget(existing_label); if (!is_mirror) existing_label->hide(); QHBoxLayout *const spin_box_layout = new QHBoxLayout(); QLabel *const add_mirrors_label = new QLabel(i18n("Add mirror legs: ")); add_mirrors_label->setBuddy(m_add_mirrors_spin); spin_box_layout->addWidget(add_mirrors_label); spin_box_layout->addWidget(m_add_mirrors_spin); add_mirror_box_layout->addLayout(spin_box_layout); add_mirror_box_layout->addStretch(); if (m_change_log) { m_type_combo->setCurrentIndex(0); m_type_combo->hide(); type_label->hide(); add_mirror_box->hide(); } else { if(is_mirror) { add_mirror_box->setTitle(i18n("Add mirror legs")); m_type_combo->hide(); type_label->hide(); } else { add_mirror_box->setTitle(i18n("Convert to mirror")); if (LvmConfig::getMirrorSegtypeDefault() == QString("mirror")) m_type_combo->setCurrentIndex(0); else m_type_combo->setCurrentIndex(1); connect(m_type_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(enableTypeOptions(int))); connect(m_type_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(resetOkButton())); } } m_log_box = new QGroupBox(i18n("Mirror logging")); QVBoxLayout *const log_box_layout = new QVBoxLayout; m_core_log_button = new QRadioButton(i18n("Memory based log")); m_disk_log_button = new QRadioButton(i18n("Single disk based log")); m_mirrored_log_button = new QRadioButton(i18n("Mirrored disk based log")); log_box_layout->addWidget(m_mirrored_log_button); log_box_layout->addWidget(m_disk_log_button); log_box_layout->addWidget(m_core_log_button); m_log_box->setLayout(log_box_layout); center_layout->addWidget(m_log_box); if (!is_mirror || m_change_log) { if (m_change_log) { m_log_box->setEnabled(true); if (m_lv->getLogCount() == 0) m_core_log_button->setChecked(true); else if (m_lv->getLogCount() == 1) m_disk_log_button->setChecked(true); else m_mirrored_log_button->setChecked(true); } else { m_disk_log_button->setChecked(true); } } else { m_log_box->hide(); } if (m_change_log && (m_lv->getLogCount() == 2)) { m_log_widget = buildLogWidget(); center_layout->addWidget(m_log_widget); enableLogWidget(); connect(m_disk_log_button, SIGNAL(toggled(bool)), this, SLOT(enableLogWidget())); connect(m_core_log_button, SIGNAL(toggled(bool)), this, SLOT(enableLogWidget())); connect(m_mirrored_log_button, SIGNAL(toggled(bool)), this, SLOT(enableLogWidget())); } else { m_log_widget = nullptr; } center_layout->addStretch(); general->setLayout(general_layout); return general; } QWidget *ChangeMirrorDialog::buildLogWidget() { QWidget *const no_change = new QWidget(); QVBoxLayout *const no_change_layout = new QVBoxLayout(); QLabel *const no_change_label = new QLabel(i18n("No change")); no_change_label->setAlignment(Qt::AlignCenter); no_change_layout->addWidget(no_change_label); no_change->setLayout(no_change_layout); QWidget *const no_log = new QWidget(); QVBoxLayout *const no_log_layout = new QVBoxLayout(); QLabel *const no_log_label = new QLabel(i18n("Remove both disk logs")); no_log_label->setAlignment(Qt::AlignCenter); no_log_layout->addWidget(no_log_label); no_log->setLayout(no_log_layout); QStringList names; for (auto log : m_lv->getAllChildrenFlat()) { if (log->isLvmMirrorLog() && !log->isMirror()) names << log->getPvNamesAll(); } if (names.size() > 0) m_log_one = new NoMungeRadioButton(names[0]); else m_log_one = new NoMungeRadioButton(""); if (names.size() > 1) m_log_two = new NoMungeRadioButton(names[1]); else m_log_two = new NoMungeRadioButton(""); m_log_one->setChecked(true); QWidget *const one_log = new QWidget(); QVBoxLayout *const one_log_layout = new QVBoxLayout(); one_log_layout->addWidget(m_log_one); one_log_layout->addWidget(m_log_two); one_log->setLayout(one_log_layout); m_log_stack = new QStackedWidget(); m_log_stack->addWidget(no_change); m_log_stack->addWidget(one_log); m_log_stack->addWidget(no_log); QVBoxLayout *const layout = new QVBoxLayout(); QGroupBox *const log_box = new QGroupBox("Mirror log to remove"); layout->addWidget(m_log_stack); log_box->setLayout(layout); return log_box; } QWidget *ChangeMirrorDialog::buildPhysicalTab(const bool isRaidMirror) { QWidget *const physical = new QWidget; QVBoxLayout *const physical_layout = new QVBoxLayout(); m_space_list = getPvSpaceList(); m_pv_box = new PvGroupBox(m_space_list, m_lv->getPolicy(), m_lv->getVg()->getPolicy()); physical_layout->addWidget(m_pv_box); physical_layout->addStretch(); QHBoxLayout *const h_layout = new QHBoxLayout(); QVBoxLayout *const lower_layout = new QVBoxLayout(); physical_layout->addLayout(h_layout); h_layout->addStretch(); h_layout->addLayout(lower_layout); h_layout->addStretch(); m_stripe_box = new QGroupBox(i18n("Volume striping")); QVBoxLayout *const striped_layout = new QVBoxLayout(); m_stripe_box->setLayout(striped_layout); m_stripe_size_combo = new QComboBox(); for (int n = 2; (pow(2, n) * 1024) <= m_lv->getVg()->getExtentSize() ; n++) { m_stripe_size_combo->addItem(QString("%1").arg(pow(2, n)) + " KiB"); m_stripe_size_combo->setItemData(n - 2, QVariant((int) pow(2, n)), Qt::UserRole); if ((n - 2) < 5) m_stripe_size_combo->setCurrentIndex(n - 2); } QLabel *const stripe_size = new QLabel(i18n("Stripe Size: ")); m_stripe_spin = new QSpinBox(); m_stripe_spin->setMinimum(1); m_stripe_spin->setSpecialValueText(i18n("none")); m_stripe_spin->setMaximum(m_lv->getVg()->getPvCount()); stripe_size->setBuddy(m_stripe_size_combo); QHBoxLayout *const stripe_size_layout = new QHBoxLayout(); stripe_size_layout->addWidget(stripe_size); stripe_size_layout->addWidget(m_stripe_size_combo); QLabel *const stripes_number = new QLabel(i18n("Number of stripes: ")); stripes_number->setBuddy(m_stripe_spin); QHBoxLayout *const stripes_number_layout = new QHBoxLayout(); stripes_number_layout->addWidget(stripes_number); stripes_number_layout->addWidget(m_stripe_spin); striped_layout->addLayout(stripe_size_layout); striped_layout->addLayout(stripes_number_layout); m_error_stack = new QStackedWidget(); QWidget *const error_widget = new QWidget(); QWidget *const blank_widget = new QWidget(); m_error_stack->addWidget(error_widget); m_error_stack->addWidget(blank_widget); QHBoxLayout *const error_layout = new QHBoxLayout(); QVBoxLayout *const error_right_layout = new QVBoxLayout(); QLabel *const stripe_error1 = new QLabel(""); stripe_error1->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(32, 32)); QLabel *const stripe_error2 = new QLabel(i18n("The number of extents: %1 must be evenly divisible by the number of stripes", m_lv->getExtents())); stripe_error2->setWordWrap(true); error_layout->addWidget(stripe_error1); error_right_layout->addWidget(stripe_error2); error_layout->addLayout(error_right_layout); error_widget->setLayout(error_layout); striped_layout->addWidget(m_error_stack); lower_layout->addWidget(m_stripe_box); if (m_change_log || isRaidMirror) { m_stripe_box->hide(); m_stripe_box->setEnabled(false); } lower_layout->addStretch(); physical->setLayout(physical_layout); return physical; } QStringList ChangeMirrorDialog::getLogPvs() { QStringList pvs; if (m_lv->isLvmMirror()) { for (auto child : m_lv->getAllChildrenFlat()) { if (child->isLvmMirrorLog() && !child->isMirror()) pvs << child->getPvNamesAll(); } } pvs.removeDuplicates(); return pvs; } QStringList ChangeMirrorDialog::getImagePvs() { QStringList pvs; if (m_lv->isMirror()) { for (auto child : m_lv->getAllChildrenFlat()) { if (child->isMirrorLeg() && !child->isMirror() && !child->isLvmMirrorLog()) pvs << child->getPvNamesAll(); } } else { pvs << m_lv->getPvNamesAll(); } pvs.removeDuplicates(); return pvs; } // If a pv holds all or part of a mirror image return true, else false. // Also return true for pvs used by the current lv if that lv is being // changed into a mirror. bool ChangeMirrorDialog::pvHasImage(QString const pv) { for (auto img_pv : m_image_pvs) { if (pv == img_pv) return true; } return false; } // If a pv holds all or part of a log return true, else false. bool ChangeMirrorDialog::pvHasLog(QString const pv) { for (auto log_pv : m_log_pvs) { if (pv == log_pv) return true; } return false; } QList> ChangeMirrorDialog::getPvSpaceList() { QList> list; const bool separate = LvmConfig::getMirrorLogsRequireSeparatePvs(); const bool islvm = m_lv->isLvmMirror(); const QList available_pvs = m_lv->getVg()->getPhysicalVolumes(); if (m_lv->isMirror() && !m_change_log) { for (auto pv : available_pvs) { if (pv->isAllocatable() && (pv->getRemaining() > 0) && !pvHasImage(pv->getMapperName())) { if (separate && islvm) { if (!pvHasLog(pv->getMapperName())) list << QSharedPointer(new PvSpace(pv, pv->getRemaining(), pv->getContiguous())); } else { list << QSharedPointer(new PvSpace(pv, pv->getRemaining(), pv->getContiguous())); } } } } else if (m_change_log) { for (auto pv : available_pvs) { if (pv->isAllocatable() && (pv->getRemaining() > 0) && !pvHasLog(pv->getMapperName())) { if (separate) { if (!pvHasImage(pv->getMapperName())) list << QSharedPointer(new PvSpace(pv, pv->getRemaining(), pv->getContiguous())); } else { list << QSharedPointer(new PvSpace(pv, pv->getRemaining(), pv->getContiguous())); } } } } else { // not a mirror for (auto pv : available_pvs) { if (pv->isAllocatable() && (pv->getRemaining() > 0) && !pvHasImage(pv->getMapperName())) list << QSharedPointer(new PvSpace(pv, pv->getRemaining(), pv->getContiguous())); } } return list; } /* Here we create a string based on all the options that the user chose in the dialog and feed that to "lvconvert" */ QStringList ChangeMirrorDialog::arguments() { QStringList args; args << "lvconvert"; if (!m_lv->isMirror()) { if (m_type_combo->currentIndex() == 1) args << "--type" << "raid1"; else args << "--type" << "mirror" << "--regionsize" << "512k"; } if (m_change_log || (!m_lv->isMirror() && m_type_combo->currentIndex() == 0)) { if (m_core_log_button->isChecked()) args << "--mirrorlog" << "core"; else if (m_mirrored_log_button->isChecked()) args << "--mirrorlog" << "mirrored"; else args << "--mirrorlog" << "disk"; } if (!m_change_log) { args << "--mirrors" << QString("+%1").arg(m_add_mirrors_spin->value()); if (m_stripe_spin->value() > 1) { args << "--stripes" << QString("%1").arg(m_stripe_spin->value()); args << "--stripesize" << (m_stripe_size_combo->currentText()).remove("KiB").trimmed(); } } args << "--background" << m_lv->getFullName(); if (m_log_widget) { if (m_disk_log_button->isChecked()) { if (m_log_one->isChecked()) args << m_log_one->getUnmungedText(); else args << m_log_two->getUnmungedText(); } } else { if (m_pv_box->getPolicy() <= ANYWHERE) // don't pass INHERITED_* args << "--alloc" << policyToString(m_pv_box->getEffectivePolicy()); args << m_pv_box->getNames(); } return args; } /* This function is supposed to find the space needed for a log of a new mirror. The formula is not reliable though and the --regionsize parameter can be ignored by the lvm tools. */ long long ChangeMirrorDialog::getNewLogSize() { /* long long size = LvmConfig::getMirrorRegionSize(); size = 1 + m_lv->getExtents() / (size * 8); return size * m_lv->getVg()->getExtentSize(); */ // reserve 4 megs ... and hope its enough return ((0x3fffff / m_lv->getVg()->getExtentSize()) + 1) * m_lv->getVg()->getExtentSize(); } int ChangeMirrorDialog::getNewLogCount() { const bool is_mirror = m_lv->isMirror(); int count = 0; if (m_change_log || (!is_mirror && (m_type_combo->currentIndex() == 0))) { if (m_disk_log_button->isChecked()) count = 1; else if (m_mirrored_log_button->isChecked()) count = 2; else count = 0; } return count; } /* Enable or disable the OK button based on having enough physical volumes checked. At least one pv for each mirror leg and one or two for the log(s) if separate pvs are needed. We also total up the space required. */ void ChangeMirrorDialog::resetOkButton() { int new_stripe_count = 1; int total_stripes = 0; // stripes per mirror * added mirrors const int new_log_count = getNewLogCount(); const bool is_lvm = m_lv->isMirror() && !m_lv->isRaid(); if (!m_change_log) { if (!validateStripeSpin()) { enableButtonOk(false); return; } if (m_stripe_spin->value() > 1) new_stripe_count = m_stripe_spin->value(); total_stripes = m_add_mirrors_spin->value() * new_stripe_count; } else if (m_change_log && (m_lv->getLogCount() == 2)) { if (new_log_count == 2) enableButtonOk(false); else enableButtonOk(true); return; } if (is_lvm) { if (m_change_log && (m_lv->getLogCount() == new_log_count)) { enableButtonOk(false); return; } else if (!m_change_log && !(total_stripes > 0)) { enableButtonOk(false); return; } } else if (!is_lvm && total_stripes <= 0) { enableButtonOk(false); return; } QList available_pv_bytes; int logs = 0; if (!getAvailableByteList(available_pv_bytes, logs, total_stripes)) { enableButtonOk(false); return; } /* if there are not enough new mirror legs to pair with any unhandled new logs we put the logs on selected pvs and discard those pvs. */ while (!available_pv_bytes.isEmpty() && logs > total_stripes) { available_pv_bytes.removeFirst(); --logs; } if (available_pv_bytes.isEmpty() && logs > 0) { enableButtonOk(false); return; } QList stripe_pv_bytes; for (int x = 0; x < total_stripes; ++x) { if (x < logs) stripe_pv_bytes.append(0 - getNewLogSize()); else stripe_pv_bytes.append(0); } if (total_stripes) { while (available_pv_bytes.size()) { qSort(available_pv_bytes); qSort(stripe_pv_bytes); stripe_pv_bytes[0] += available_pv_bytes.takeLast(); } qSort(stripe_pv_bytes); // reserve one extent for raid metadata if ((m_type_combo && m_type_combo->currentIndex() == 1) || m_lv->isRaid()) { if ((stripe_pv_bytes[0] - 1) >= (m_lv->getSize() / new_stripe_count)) enableButtonOk(true); else enableButtonOk(false); } else { if (stripe_pv_bytes[0] >= (m_lv->getSize() / new_stripe_count)) enableButtonOk(true); else enableButtonOk(false); } return; } else { enableButtonOk(true); return; } enableButtonOk(false); return; } void ChangeMirrorDialog::enableTypeOptions(int index) { if (index == 0) { m_log_box->setEnabled(true); if (m_stripe_box) // The physical tab is not always defined m_stripe_box->setEnabled(true); } else { m_log_box->setEnabled(false); m_disk_log_button->setChecked(true); if (m_stripe_spin) m_stripe_spin->setValue(1); if (m_stripe_box) m_stripe_box->setEnabled(false); } } void ChangeMirrorDialog::setLogRadioButtons() { if (m_change_log) { if (m_lv->getLogCount() == 2) m_mirrored_log_button->setChecked(true); else if (m_lv->getLogCount() == 1) m_disk_log_button->setChecked(true); else m_core_log_button->setChecked(true); } else if (!m_lv->isLvmMirror()) { m_disk_log_button->setChecked(true); } resetOkButton(); } void ChangeMirrorDialog::commit() { hide(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); ProcessProgress add_mirror(arguments()); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } bool ChangeMirrorDialog::validateStripeSpin() { if (m_stripe_spin->value() > 1) { if (m_lv->getExtents() % m_stripe_spin->value()) { m_error_stack->setCurrentIndex(0); // unworkable stripe count return false; } else { m_error_stack->setCurrentIndex(1); // valid stripe count return true; } } else { m_error_stack->setCurrentIndex(1); return true; } } void ChangeMirrorDialog::enableLogWidget() { if(m_mirrored_log_button->isChecked()) m_log_stack->setCurrentIndex(0); else if (m_disk_log_button->isChecked()) m_log_stack->setCurrentIndex(1); else m_log_stack->setCurrentIndex(2); } /* Generate a list of the space available on each selected pv usable for a new mirror leg. If a pv is selected by the user that cannot be used for a leg, it tries to put a log there. The unhandledLogs parameter returns the number of logs that will need to go with the legs. The stripes parameter is the stripes per leg multiplied by the number of new legs. Returns true on success. */ bool ChangeMirrorDialog::getAvailableByteList(QList &byte_list, int &unhandledLogs, const int stripes) { const AllocationPolicy policy = m_pv_box->getEffectivePolicy(); const bool separate_logs = LvmConfig::getMirrorLogsRequireSeparatePvs(); unhandledLogs = 0; int logs_added = getNewLogCount() - m_lv->getLogCount(); if (logs_added < 0) logs_added = 0; if ((policy == CONTIGUOUS) || separate_logs) { // contiguous allocation also implies separate logs for (auto pv_name : m_pv_box->getNames()) { if (!pvHasImage(pv_name) && !pvHasLog(pv_name)) { for (auto available_space : m_space_list) { if (available_space->pv->getMapperName() == pv_name) { if (policy == CONTIGUOUS) byte_list << available_space->contiguous; else byte_list << available_space->normal; } } } } qSort(byte_list); while (!byte_list.isEmpty() && byte_list[0] <= 0) byte_list.removeAt(0); while (logs_added > 0 && byte_list.size()) { byte_list.removeFirst(); --logs_added; } if (logs_added > 0) // Too many logs for the selected pvs, so just bail out return false; if (policy == CONTIGUOUS) { while (byte_list.size() > stripes) byte_list.removeFirst(); } } else { for (auto pv_name : m_pv_box->getNames()) { if (pvHasImage(pv_name) && !pvHasLog(pv_name)) { --logs_added; } else if (!pvHasImage(pv_name)) { for (auto available_space : m_space_list) { if (available_space->pv->getMapperName() == pv_name) byte_list << available_space->normal; } } } qSort(byte_list); while (!byte_list.isEmpty() && byte_list[0] <= 0) byte_list.removeAt(0); if (logs_added <= 0) unhandledLogs = 0; else unhandledLogs = logs_added; if (unhandledLogs > byte_list.size()) return false; } return true; } kvpm-0.9.10/kvpm/pvmove.cpp0000644000175000017500000005005312770324126016006 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvmove.h" #include "logvol.h" #include "masterlist.h" #include "misc.h" #include "processprogress.h" #include "physvol.h" #include "pvgroupbox.h" #include "volgroup.h" #include #include #include #include #include #include #include #include #include #include #include #include #include struct NameAndRange { QString name; // Physical volume name QString name_range; // name + range of extents ie: /dev/sda1:10-100 and just name if no range specified uint64_t start; // Starting extent uint64_t end; // Last extent long long used; // extents in use }; bool restart_pvmove() { const QStringList args = QStringList() << "pvmove"; const QString message = i18n("Do you wish to restart all interrupted physical volume moves?"); if (KMessageBox::questionYesNo(nullptr, message) == KMessageBox::Yes) { ProcessProgress resize(args); return true; } else { return false; } } bool stop_pvmove() { const QStringList args = QStringList() << "pvmove" << "--abort"; const QString message = i18n("Do you wish to abort all physical volume moves currently in progress?"); if (KMessageBox::questionYesNo(nullptr, message) == KMessageBox::Yes) { ProcessProgress resize(args); return true; } else { return false; } } // Whole pv move PVMoveDialog::PVMoveDialog(PhysVol *const physicalVolume, QWidget *parent) : KvpmDialog(parent) { m_vg = physicalVolume->getVg(); m_move_segment = false; const QString name = physicalVolume->getMapperName(); const LvList lvs = m_vg->getLogicalVolumes(); NameAndRange *nar = new NameAndRange; nar->name = name; nar->name_range = name; nar->used = (physicalVolume->getSize() - physicalVolume->getRemaining()) / m_vg->getExtentSize(); m_sources.append(nar); QList target_pvs = removeFullTargets(m_vg->getPhysicalVolumes()); if (m_sources.size() == 1) target_pvs = removeForbiddenTargets(target_pvs, m_sources[0]->name); if (!hasMovableExtents()) { preventExec(); KMessageBox::sorry(nullptr, i18n("None of the extents on this volume can be moved")); } if (willExec()) buildDialog(target_pvs); } // pv move only on one lv PVMoveDialog::PVMoveDialog(LogVol *const logicalVolume, int const segment, QWidget *parent) : KvpmDialog(parent), m_lv(logicalVolume), m_segment(segment) { m_vg = m_lv->getVg(); if (!hasMovableExtents()){ preventExec(); KMessageBox::sorry(nullptr, i18n("None of the extents on this volume can be moved")); } else { QList target_pvs = removeFullTargets(m_vg->getPhysicalVolumes()); if (segment >= 0) { setupSegmentMove(segment); m_move_segment = true; if (m_sources.size() == 1) target_pvs = removeForbiddenTargets(target_pvs, m_sources[0]->name); } else { setupFullMove(); m_move_segment = false; if (m_sources.size() == 1) target_pvs = removeForbiddenTargets(target_pvs, m_sources[0]->name); } if (willExec()) { buildDialog(target_pvs); connect(this, SIGNAL(okClicked()), this, SLOT(commit())); } } } PVMoveDialog::~PVMoveDialog() { for (int x = 0; x < m_sources.size(); x++) delete m_sources[x]; } QList PVMoveDialog::removeFullTargets(QList targets) { for (int x = targets.size() - 1; x >= 0; --x) { if (targets[x]->getRemaining() <= 0 || !targets[x]->isAllocatable()) targets.removeAt(x); } /* If there is only one physical volume in the group or they are all full then a pv move will have no place to go */ if (targets.isEmpty()) { KMessageBox::sorry(nullptr, i18n("There are no allocatable physical volumes with space to move to")); preventExec(); } return targets; } void PVMoveDialog::buildDialog(QList targets) { bool use_si_units; KConfigSkeleton skeleton; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", use_si_units, false); KFormat::BinaryUnitDialect dialect; if (use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; setCaption(i18n("Move Physical Extents")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout; layout->addWidget(bannerWidget()); layout->addSpacing(5); QGroupBox *const source_group = new QGroupBox(i18n("Source Physical Volumes")); QVBoxLayout *const source_layout = new QVBoxLayout; QGridLayout *const radio_layout = new QGridLayout(); source_layout->addLayout(radio_layout); source_group->setLayout(source_layout); layout->addWidget(source_group); QHBoxLayout *const lower_layout = new QHBoxLayout; layout->addLayout(lower_layout); QList> pv_space_list; for (auto pv : targets) { pv_space_list << QSharedPointer(new PvSpace(pv, pv->getRemaining(), pv->getContiguous())); } m_pv_box = new PvGroupBox(pv_space_list, m_vg->getPolicy(), NO_POLICY, true); lower_layout->addWidget(m_pv_box); const int radio_count = m_sources.size(); NoMungeRadioButton *radio_button = nullptr; if (radio_count > 1) { m_radio_label = new QLabel(); source_layout->addSpacing(10); source_layout->addWidget(m_radio_label); for (int x = 0; x < radio_count; x++) { if (m_move_segment) { m_pv_used_space = (1 + m_sources[x]->end - m_sources[x]->start) * m_vg->getExtentSize(); radio_button = new NoMungeRadioButton(QString("%1 %2").arg(m_sources[x]->name_range).arg(KFormat().formatByteSize(m_pv_used_space, 1, dialect))); radio_button->setAlternateText(m_sources[x]->name); } else if (m_lv) { m_pv_used_space = m_lv->getSpaceUsedOnPv(m_sources[x]->name); radio_button = new NoMungeRadioButton(QString("%1 %2").arg(m_sources[x]->name).arg(KFormat().formatByteSize(m_pv_used_space, 1, dialect))); radio_button->setAlternateText(m_sources[x]->name); } else { m_pv_used_space = m_sources[x]->used; radio_button = new NoMungeRadioButton(QString("%1 %2").arg(m_sources[x]->name).arg(KFormat().formatByteSize(m_pv_used_space, 1, dialect))); radio_button->setAlternateText(m_sources[x]->name); } if (radio_count < 11) radio_layout->addWidget(radio_button, x % 5, x / 5); else if (radio_count % 3 == 0) radio_layout->addWidget(radio_button, x % (radio_count / 3), x / (radio_count / 3)); else radio_layout->addWidget(radio_button, x % ((radio_count + 2) / 3), x / ((radio_count + 2) / 3)); m_radio_buttons.append(radio_button); connect(radio_button, SIGNAL(toggled(bool)), this, SLOT(disableTargets())); connect(radio_button, SIGNAL(toggled(bool)), this, SLOT(setRadioExtents())); if (!x) radio_button->setChecked(true); } } else { source_group->setTitle(i18n("Source Physical Volume")); auto src = m_sources[0]; if (m_move_segment) { m_pv_used_space = (1 + src->end - src->start) * m_vg->getExtentSize(); radio_layout->addWidget(new QLabel(QString("%1 %2").arg(src->name_range).arg(KFormat().formatByteSize(m_pv_used_space, 1, dialect)))); } else if (m_lv) { m_pv_used_space = m_lv->getSpaceUsedOnPv(src->name); radio_layout->addWidget(new QLabel(QString("%1 %2").arg(src->name).arg(KFormat().formatByteSize(m_pv_used_space, 1, dialect)))); } else { m_pv_used_space = movableExtents() * m_vg->getExtentSize(); radio_layout->addWidget(new QLabel(QString("%1 %2").arg(src->name).arg(KFormat().formatByteSize(m_pv_used_space, 1, dialect)))); source_layout->addWidget(singleSourceWidget()); } } dialog_body->setLayout(layout); connect(m_pv_box, SIGNAL(stateChanged()), this, SLOT(disableTargets())); disableTargets(); resetOkButton(); } void PVMoveDialog::resetOkButton() { long long free_space_total = 0; if(m_pv_box->getEffectivePolicy() == CONTIGUOUS) { for (auto space : m_pv_box->getRemainingSpaceList()) { if (space > free_space_total) free_space_total = space; } } else { free_space_total = m_pv_box->getRemainingSpace(); } long long needed_space_total = 0; QString pv_name; if (m_lv) { if (m_radio_buttons.size() > 1) { for (auto radio : m_radio_buttons) { if (radio->isChecked()) { pv_name = radio->getAlternateText(); needed_space_total = m_lv->getSpaceUsedOnPv(pv_name); } } } else { pv_name = m_sources[0]->name; needed_space_total = m_lv->getSpaceUsedOnPv(pv_name); } } else { needed_space_total = m_pv_used_space; } if (free_space_total < needed_space_total) enableButtonOk(false); else enableButtonOk(true); } QStringList PVMoveDialog::arguments() { QStringList args = QStringList() << "pvmove" << "--background"; QString source; if (m_lv) { args << "--name"; args << m_lv->getFullName(); } if (m_pv_box->getPolicy() <= ANYWHERE) // don't pass INHERITED_* args << "--alloc" << policyToString(m_pv_box->getEffectivePolicy()); if (m_sources.size() > 1) { for (int x = m_sources.size() - 1; x >= 0; x--) { if (m_radio_buttons[x]->isChecked()) source = m_sources[x]->name_range; } } else { source = m_sources[0]->name_range; } args << source; args << m_pv_box->getNames(); // target(s) return args; } void PVMoveDialog::setupSegmentMove(int segment) { const QStringList names = m_lv->getPvNames(segment); // source pv name const int stripes = m_lv->getSegmentStripes(segment); // source pv stripe count const long long extents = m_lv->getSegmentExtents(segment); // extent count const QList starts = m_lv->getSegmentStartingExtent(segment); // lv's first extent on pv NameAndRange *nar = nullptr; for (int x = 0; x < names.size(); ++x) { nar = new NameAndRange; nar->name = names[x]; nar->start = starts[x]; nar->end = starts[x] + (extents / stripes) - 1; nar->name_range = QString("%1:%2-%3").arg(nar->name).arg(nar->start).arg(nar->end); nar->used = 1 + (nar->end - nar->start); m_sources.append(nar); } } void PVMoveDialog::setupFullMove() { NameAndRange *nar = nullptr; PhysVol *pv = nullptr; for (auto pvname : m_lv->getPvNamesAllFlat()) { if ( (pv = m_vg->getPvByName(pvname)) ) { nar = new NameAndRange(); nar->name = pvname; nar->name_range = pvname; nar->used = (pv->getSize() - pv->getRemaining()) / m_vg->getExtentSize(); m_sources.append(nar); } } } void PVMoveDialog::commit() { hide(); ProcessProgress move(arguments()); return; } QWidget* PVMoveDialog::bannerWidget() { QWidget *const banner = new QWidget; QVBoxLayout *const layout = new QVBoxLayout; QLabel *label = new QLabel; label->setAlignment(Qt::AlignCenter); layout->addWidget(label); if (m_lv) { label->setText(i18n("Move only physical extents on:")); label = new QLabel("" + m_lv->getFullName() + ""); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); } else { label->setText(i18n("Move physical extents")); } banner->setLayout(layout); return banner; } QWidget* PVMoveDialog::singleSourceWidget() { bool use_si_units; KConfigSkeleton skeleton; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", use_si_units, false); QLabel *label = nullptr; KFormat::BinaryUnitDialect dialect; if (use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; QWidget *const widget = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); QGridLayout *const grid = new QGridLayout(); grid->setColumnStretch(1,1); grid->setColumnStretch(2,2); layout->addLayout(grid); widget->setLayout(layout); label = new QLabel(i18n("Logical Volumes To Move")); label->setAlignment(Qt::AlignCenter); grid->addWidget(label, 0, 0, 1, -1); const QStringList lv_names = getLvNames(); for (int x = 0; x < lv_names.size(); ++x) { LogVol *const lv = m_vg->getLvByName(lv_names[x]); if (lv) { label = new QLabel(lv_names[x]); grid->addWidget(label, x + 1, 0); label = new QLabel(QString("%1").arg(KFormat().formatByteSize(lv->getSpaceUsedOnPv(m_sources[0]->name), 1, dialect))); label->setAlignment(Qt::AlignRight); grid->addWidget(label, x + 1, 1); if (!isMovable(lv)) { label = new QLabel(i18n("")); label->setAlignment(Qt::AlignCenter); grid->addWidget(label, x + 1, 2); } } } layout->addSpacing(10); layout->addWidget(new QLabel(i18n("Movable space: %1", KFormat().formatByteSize(movableExtents() * m_vg->getExtentSize(), 1, dialect)))); layout->addWidget(new QLabel(i18n("Movable extents: %1", movableExtents()))); return widget; } bool PVMoveDialog::hasMovableExtents() { bool movable = false; if (m_lv) { // move only lv movable = isMovable(m_lv); } else { // move whole pv for (auto name : getLvNames()) { LogVol *const lv = m_vg->getLvByName(name); if (lv && isMovable(lv)) movable = true; } } return movable; } // The names of each lv on the source pv when moving a whole pv QStringList PVMoveDialog::getLvNames() { QList segs; segs = m_vg->getPvByName(m_sources[0]->name)->sortByExtent(); QStringList lv_names; for (int x = 0; x < segs.size(); ++x) { lv_names.append(segs[x]->lv_name); delete segs[x]; } lv_names.removeDuplicates(); lv_names.sort(); return lv_names; } void PVMoveDialog::setRadioExtents() { if (m_radio_label) { for (auto radio : m_radio_buttons) { if (radio->isChecked()) { PhysVol *const pv = m_vg->getPvByName(radio->getAlternateText()); const long long used = (pv->getSize() - pv->getRemaining()) / m_vg->getExtentSize(); m_radio_label->setText(i18n("Extents: %1", used)); } } } } bool PVMoveDialog::isMovable(LogVol *lv) { if (lv->isThinVolume() || lv->isLvmMirror() || lv->isLvmMirrorLeg() || lv->isLvmMirrorLog() || lv->isCowSnap() || lv->isCowOrigin()) { return false; } else { return true; } } // Number of movable extents when moving a *whole* pv long long PVMoveDialog::movableExtents() { long long extents = 0; for (auto name : getLvNames()) { LogVol *const lv = m_vg->getLvByName(name); if (lv && isMovable(lv)) extents += lv->getSpaceUsedOnPv(m_sources[0]->name) / m_vg->getExtentSize(); } return extents; } void PVMoveDialog::disableTargets() { QString source; if (!m_radio_buttons.isEmpty()) { for (auto radio : m_radio_buttons) { if (radio->isChecked()) source = radio->getAlternateText(); } } else { source = m_sources[0]->name; } m_pv_box->disableChecks(getForbiddenTargets(m_lv, source)); resetOkButton(); } /* don't allow source and target to be the same pv or move a pv to another it is striped with */ QStringList PVMoveDialog::getForbiddenTargets(LogVol *const lv, const QString source) { /* Once lvm2 2.02.99 or higher is released this will need to be tested again. What happens if parts can be moved to another pv but some parts cannot? How will allocation policy work with raid legs during pvmove? */ QStringList forbidden = QStringList() << source; if(m_pv_box->getEffectivePolicy() == ANYWHERE) return forbidden; if (lv) { if (lv->isRaid()) { if (lv->getPvNamesAllFlat().contains(source)) { for (auto image : lv->getRaidImageVolumes()) { QStringList pvs = QStringList() << image->getPvNamesAllFlat(); LogVol *meta = image->getRaidImageMetadata(); if (meta) pvs << meta->getPvNamesAllFlat(); if (!pvs.contains(source)) forbidden << pvs; } } } else if (lv->isThinPool()) { for (auto data : lv->getThinDataVolumes()) forbidden << getForbiddenTargets(data, source); for (auto meta : lv->getThinMetadataVolumes()) forbidden << getForbiddenTargets(meta, source); } else { if (lv->isRaidImage() || lv->isRaidMetadata()) { // don't allow moving pvs from one raid leg to another LogVol *image, *meta; if (lv->isRaidImage()) { meta = lv->getRaidImageMetadata(); image = lv; } else { image = lv->getRaidMetadataImage(); meta = lv; } auto parent = lv->getParentRaid(); if (parent) { for (auto leg : parent->getRaidImageVolumes()) { if (image != leg) forbidden << leg->getPvNamesAllFlat(); } for (auto leg : parent->getRaidMetadataVolumes()) { if (meta != leg) forbidden << leg->getPvNamesAllFlat(); } } } if (m_move_segment) { forbidden << lv->getPvNames(m_segment); } else { for (int seg = lv->getSegmentCount() - 1; seg >= 0; --seg) { if (lv->getSegmentStripes(seg) > 1 && lv->getPvNames(seg).contains(source)) forbidden << lv->getPvNames(seg); } } } } else { // Whole pv move for (auto lv : m_vg->getLogicalVolumes()) forbidden << getForbiddenTargets(lv, source); } forbidden.removeDuplicates(); return forbidden; } /* if there is only one possible source then we remove it from the target list completely */ QList PVMoveDialog::removeForbiddenTargets(QList targets, const QString source) { QStringList forbidden = QStringList() << source; for (int x = targets.size() - 1 ; x >= 0; --x) { for (auto remove : forbidden) { if (targets[x]->getMapperName() == remove) targets.removeAt(x); } } return targets; } kvpm-0.9.10/kvpm/devicetab.h0000644000175000017500000000245012770324126016063 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef DEVICETAB_H #define DEVICETAB_H #include #include #include class QSplitter; class QScrollArea; class QTreeWidgetItem; class QVBoxLayout; class KToolBar; class DeviceActions; class DeviceTree; class DevicePropertiesStack; class DeviceSizeChart; class StorageDevice; class DeviceTab : public KMainWindow { Q_OBJECT QVBoxLayout *m_layout = nullptr; DeviceTree *m_tree = nullptr; QSplitter *m_tree_properties_splitter = nullptr; DeviceActions *m_device_actions = nullptr; DeviceSizeChart *m_size_chart = nullptr; DevicePropertiesStack *m_device_stack = nullptr; QScrollArea *setupPropertyStack(); KToolBar *buildDeviceToolBar(); void readConfig(); public: DeviceTab(QWidget *parent = nullptr); void rescan(QList Devices); private slots: void deviceContextMenu(QTreeWidgetItem *item); }; #endif kvpm-0.9.10/kvpm/devicesizechartseg.h0000644000175000017500000000156212770324126020013 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef STORAGEDEVICESIZECHARTSEG_H #define STORAGEDEVICESIZECHARTSEG_H #include class QTreeWidgetItem; class StoragePartition; class DeviceChartSeg : public QFrame { Q_OBJECT QTreeWidgetItem *m_item; StoragePartition *m_partition; public: explicit DeviceChartSeg(QTreeWidgetItem *const storageItem, QWidget *parent = nullptr); public slots: void popupContextMenu(); signals: void deviceMenuRequested(QTreeWidgetItem *item); }; #endif kvpm-0.9.10/kvpm/pvreduce.h0000644000175000017500000000077012770324126015755 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVREDUCE_H #define PVREDUCE_H class QString; long long pv_reduce(QString path, long long new_size); #endif kvpm-0.9.10/kvpm/lvpropertiesstack.h0000644000175000017500000000175612770324126017731 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVPROPERTIESSTACK_H #define LVPROPERTIESSTACK_H #include #include class QLabel; class QScrollArea; class QStackedWidget; class QTreeWidget; class QTreeWidgetItem; class VolGroup; class LVPropertiesStack : public QFrame { Q_OBJECT VolGroup *m_vg; QStackedWidget *m_stack_widget; QList m_lv_stack_list; QScrollArea *m_vscroll; QLabel *m_lv_label; public: explicit LVPropertiesStack(VolGroup *Group, QWidget *parent = 0); void loadData(); public slots: void changeLVStackIndex(QTreeWidgetItem *item, QTreeWidgetItem*); }; #endif kvpm-0.9.10/kvpm/lvsizechartseg.cpp0000644000175000017500000001345412770324126017533 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvsizechartseg.h" #include #include #include #include #include "logvol.h" #include "lvactionsmenu.h" /* This should be passed *lv = 0 if it is really free space on a volume group that is being displayed */ LVChartSeg::LVChartSeg(LogVol *const volume, const QString use, QWidget *parent) : QWidget(parent), m_lv(volume) { KConfigSkeleton skeleton; QColor ext2_color, ext3_color, ext4_color, reiser_color, reiser4_color, msdos_color, jfs_color, xfs_color, none_color, ntfs_color, swap_color, hfs_color, btrfs_color; skeleton.setCurrentGroup("FilesystemColors"); skeleton.addItemColor("ext2", ext2_color, Qt::blue); skeleton.addItemColor("ext3", ext3_color, Qt::darkBlue); skeleton.addItemColor("ext4", ext4_color, Qt::cyan); skeleton.addItemColor("btrfs", btrfs_color, Qt::yellow); skeleton.addItemColor("reiser", reiser_color, Qt::red); skeleton.addItemColor("reiser4", reiser4_color, Qt::darkRed); skeleton.addItemColor("msdos", msdos_color, Qt::darkYellow); skeleton.addItemColor("jfs", jfs_color, Qt::magenta); skeleton.addItemColor("xfs", xfs_color, Qt::darkGreen); skeleton.addItemColor("hfs", hfs_color, Qt::darkMagenta); skeleton.addItemColor("ntfs", ntfs_color, Qt::darkGray); skeleton.addItemColor("none", none_color, Qt::black); skeleton.addItemColor("swap", swap_color, Qt::lightGray); QColor mirror_color, raid1_color, raid456_color, thinvol_color, thinsnap_color, cowsnap_color, linear_color, other_color, pvmove_color, invalid_color, free_color; skeleton.setCurrentGroup("VolumeTypeColors"); skeleton.addItemColor("mirror", mirror_color, Qt::darkBlue); // lvm type mirror skeleton.addItemColor("raid1", raid1_color, Qt::blue); skeleton.addItemColor("raid456", raid456_color, Qt::cyan); skeleton.addItemColor("thinvol", thinvol_color, Qt::lightGray); skeleton.addItemColor("thinsnap", thinsnap_color, Qt::darkRed); skeleton.addItemColor("cowsnap", cowsnap_color, Qt::darkYellow); skeleton.addItemColor("linear", linear_color, Qt::darkCyan); skeleton.addItemColor("pvmove", pvmove_color, Qt::magenta); skeleton.addItemColor("other", other_color, Qt::yellow); skeleton.addItemColor("invalid", invalid_color, Qt::red); skeleton.addItemColor("free", free_color, Qt::green); int type_combo_index; skeleton.setCurrentGroup("General"); skeleton.addItemInt("type_combo", type_combo_index, 0); QColor color; if (m_lv && !m_lv->isActive() && m_lv->isValid()) { color = none_color; m_brush = QBrush(color, Qt::SolidPattern); } else if (use == "thin_pool") { color = free_color; m_brush = QBrush(color, Qt::Dense4Pattern); } else if (use == "freespace") { color = free_color; m_brush = QBrush(color, Qt::SolidPattern); } else if (m_lv && m_lv->isPvmove()) { // must come before "mirror" because it's a type of mirror color = pvmove_color; m_brush = QBrush(color, Qt::SolidPattern); } else if (m_lv && type_combo_index == 1) { if (use == "ext2") color = ext2_color; else if (use == "ext3") color = ext3_color; else if (use == "ext4") color = ext4_color; else if (use == "btrfs") color = btrfs_color; else if (use == "reiserfs") color = reiser_color; else if (use == "reiser4") color = reiser4_color; else if (use == "hfs") color = hfs_color; else if (use == "vfat") color = msdos_color; else if (use == "jfs") color = jfs_color; else if (use == "xfs") color = xfs_color; else if (use == "ntfs") color = ntfs_color; else if (use == "swap") color = swap_color; else if (use == "freespace") color = free_color; else color = none_color; m_brush = QBrush(color, Qt::SolidPattern); } else if (m_lv){ if (m_lv->isLvmMirror()) color = mirror_color; else if (m_lv->isRaid() && m_lv->isMirror()) color = raid1_color; else if (m_lv->isRaid() && !m_lv->isMirror()) color = raid456_color; else if(m_lv->isCowSnap() && m_lv->isValid()) color = cowsnap_color; else if(!m_lv->isValid()) color = invalid_color; else if (m_lv->isPvmove()) color = pvmove_color; else if (m_lv->isOrphan() || m_lv->isVirtual()) color = other_color; else color = linear_color; m_brush = QBrush(color, Qt::SolidPattern); } setContextMenuPolicy(Qt::CustomContextMenu); if (m_lv) setToolTip(m_lv->getName()); else setToolTip(i18n("free space")); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(popupContextMenu(QPoint))); } void LVChartSeg::popupContextMenu(QPoint) { emit lvMenuRequested(m_lv); } void LVChartSeg::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setBrush(m_brush); QRect rect = QRect(0, 0, this->width(), this->height()); painter.fillRect(rect, m_brush); } kvpm-0.9.10/kvpm/vgtree.h0000644000175000017500000000242012770324126015426 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGTREE_H #define VGTREE_H #include class QPoint; class QTreeWidgetItem; class LogVol; class VolGroup; class VGTree : public QTreeWidget { Q_OBJECT VolGroup *m_vg; bool m_init; // is this the initial building of the tree or a reload? bool m_show_percent, m_show_total, m_use_si_units, m_show_both; int m_fs_warn_percent; void setupContextMenu(); QTreeWidgetItem *loadItem(LogVol *lv, QTreeWidgetItem *item); void insertChildItems(LogVol *parentVolume, QTreeWidgetItem *parentItem); void insertSegmentItems(LogVol *logicalVolume, QTreeWidgetItem *item); void setViewConfig(); public: VGTree(VolGroup *const group); void loadData(); private slots: void popupContextMenu(QPoint point); void adjustColumnWidth(QTreeWidgetItem *); signals: void lvMenuRequested(QTreeWidgetItem *item); }; #endif kvpm-0.9.10/kvpm/maintabwidget.h0000644000175000017500000000243712770324126016761 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef MAINTABWIDGET_H #define MAINTABWIDGET_H #include #include class QIcon; class QTabWidget; class VolumeGroupTab; class DeviceTab; class MainTabWidget : public QWidget { Q_OBJECT QStringList m_unmunged_text; // Tab labels without amperands QTabWidget *m_tab_widget; QList m_vg_tabs; signals: void currentIndexChanged(int index); public: MainTabWidget(QWidget *parent = 0); QString getUnmungedText(const int index); void appendVolumeGroupTab(VolumeGroupTab *const page, const QIcon &icon, const QString &label); void appendDeviceTab(DeviceTab *const page, const QString & label); void deleteTab(const int index); QWidget *getWidget(const int index); int getCount(); int getCurrentIndex(); VolumeGroupTab *getVolumeGroupTab(const int index); void setIcon(const int index, const QIcon &icon); }; #endif kvpm-0.9.10/kvpm/pvpropertiesstack.cpp0000644000175000017500000000626712770324126020272 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvpropertiesstack.h" #include #include #include #include #include #include #include #include #include #include "logvol.h" #include "physvol.h" #include "pvproperties.h" #include "volgroup.h" PVPropertiesStack::PVPropertiesStack(VolGroup *volumeGroup, QWidget *parent) : QFrame(parent), m_vg(volumeGroup) { m_vscroll = new QScrollArea; m_stack_widget = new QStackedWidget(); QVBoxLayout *const vlayout = new QVBoxLayout(); QHBoxLayout *const hlayout = new QHBoxLayout(); vlayout->setMargin(0); vlayout->setSpacing(0); m_pv_label = new QLabel(); m_pv_label->setAlignment(Qt::AlignCenter); vlayout->addSpacing(2); vlayout->addWidget(m_pv_label); vlayout->addSpacing(2); vlayout->addWidget(m_vscroll); vlayout->addLayout(hlayout); m_vscroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_vscroll->setBackgroundRole(QPalette::Base); m_vscroll->setAutoFillBackground(true); m_vscroll->verticalScrollBar()->setBackgroundRole(QPalette::Window); m_vscroll->verticalScrollBar()->setAutoFillBackground(true); m_vscroll->setFrameShape(QFrame::NoFrame); setLayout(vlayout); } /* If *item points to a volume we set the widget stack to the widget with that volume's information. Else we set the stack widget index to -1, nothing */ void PVPropertiesStack::changePVStackIndex(QTreeWidgetItem *item, QTreeWidgetItem*) { const QList devices = m_vg->getPhysicalVolumes(); if (!m_stack_widget) return; if (item) { const QString pv_uuid = QVariant(item->data(0, Qt::UserRole)).toString(); for (int x = devices.size() - 1; x >= 0; --x) { if (pv_uuid == devices[x]->getUuid()) { m_stack_widget->setCurrentIndex(x); m_pv_label->setText("" + devices[x]->getMapperName() + ""); } } } else { m_stack_widget->setCurrentIndex(-1); m_pv_label->setText(""); } } void PVPropertiesStack::loadData() { const QList volumes = m_vg->getPhysicalVolumes(); if (!m_stack_widget) m_stack_widget = new QStackedWidget; for (int x = m_stack_widget->count() - 1; x >=0; --x) { QWidget *widget = m_stack_widget->widget(x); m_stack_widget->removeWidget(widget); widget->deleteLater(); } for (auto pv : volumes) m_stack_widget->addWidget(new PVProperties(pv)); if (m_vscroll->widget() == 0) m_vscroll->setWidget(m_stack_widget); if (!volumes.isEmpty()) m_stack_widget->setCurrentIndex(0); m_vscroll->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); m_vscroll->setWidgetResizable(true); } kvpm-0.9.10/kvpm/pvgroupbox.h0000644000175000017500000000534712770324126016360 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVGROUPBOX_H #define PVGROUPBOX_H #include #include #include #include #include #include #include #include #include #include #include #include #include "allocationpolicy.h" #include "misc.h" class PhysVol; class StorageBase; class PvGroupBox: public QGroupBox { Q_OBJECT bool m_target = false; // is this is a target for a pv move. KFormat::BinaryUnitDialect m_dialect; // power of 10 SI or traditional power of two units QList m_pvs; QList m_devices; QList m_pv_checks; QList m_normal; QList m_contiguous; QLabel *m_space_label = nullptr; QLabel *m_extents_label = nullptr; long long m_extent_size; QHBoxLayout *getButtons(); PolicyComboBox *m_policy_combo = nullptr; long long getLargestSelectedSpace(); void addLabelsAndButtons(QGridLayout *const layout, const int pvCount, AllocationPolicy const policy, AllocationPolicy const vgpolicy); KFormat::BinaryUnitDialect getDialect(); public: // Note: policy needs to be passed "inherited" here for new logical volumes PvGroupBox(QList> spaceList, AllocationPolicy const policy, AllocationPolicy const vgpolicy, // For lvs, to pass the policy to inherit, if needed bool const target = false, QWidget *parent = nullptr); PvGroupBox(const QList devices, const long long extentSize, QWidget *parent = nullptr); QStringList getAllNames(); // names of all pvs displayed in the box QStringList getNames(); // names of *selected* pvs long long getRemainingSpace(); // total unused space on selected pvs QList getRemainingSpaceList(); // ditto void setExtentSize(long long extentSize); void disableChecks(QStringList pvs); AllocationPolicy getPolicy(); AllocationPolicy getEffectivePolicy(); // convert inherited policy to the vg default policy signals: void stateChanged(); private slots: void calculateSpace(); void setChecksToPolicy(); public slots: void selectAll(); void selectNone(); }; #endif kvpm-0.9.10/kvpm/volumegrouptab.cpp0000644000175000017500000002334012770324126017544 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "volumegrouptab.h" #include #include #include #include #include #include #include #include "lvpropertiesstack.h" #include "lvsizechart.h" #include "physvol.h" #include "pvpropertiesstack.h" #include "pvtree.h" #include "vginfolabels.h" #include "vgremove.h" #include "vgtree.h" #include "vgwarning.h" #include "volgroup.h" #include "lvactions.h" #include "lvactionsmenu.h" #include "pvactions.h" #include "pvactionsmenu.h" VolumeGroupTab::VolumeGroupTab(VolGroup *const group, QWidget *parent) : KMainWindow(parent), m_vg(group) { QWidget *const central = new QWidget(); setCentralWidget(central); m_layout = new QVBoxLayout; central->setLayout(m_layout); m_vg_warning = new VGWarning(); m_layout->addWidget(m_vg_warning); m_lv_actions = new LVActions(group, this); m_pv_actions = new PVActions(group, this); addToolBar(buildLvToolBar()); addToolBar(buildPvToolBar()); m_vg_tree = new VGTree(m_vg); m_pv_tree = new PVTree(m_vg); connect(m_vg_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_lv_actions, SLOT(changeLv(QTreeWidgetItem*))); connect(m_vg_tree, SIGNAL(lvMenuRequested(QTreeWidgetItem*)), this, SLOT(lvContextMenu(QTreeWidgetItem*))); connect(m_pv_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_pv_actions, SLOT(changePv(QTreeWidgetItem*))); connect(m_pv_tree, SIGNAL(pvMenuRequested(QTreeWidgetItem*)), this, SLOT(pvContextMenu(QTreeWidgetItem*))); m_vg_tree->setAlternatingRowColors(true); m_pv_tree->setAlternatingRowColors(true); QSplitter *const tree_splitter = new QSplitter(Qt::Vertical); QSplitter *const lv_splitter = new QSplitter(); QSplitter *const pv_splitter = new QSplitter(); m_layout->addWidget(tree_splitter); tree_splitter->addWidget(lv_splitter); tree_splitter->addWidget(pv_splitter); lv_splitter->addWidget(m_vg_tree); pv_splitter->addWidget(m_pv_tree); tree_splitter->setStretchFactor(0, 3); tree_splitter->setStretchFactor(1, 2); QList lv_size_list; lv_size_list << 1500 << 10 ; lv_splitter->setSizes(lv_size_list); m_lv_properties_stack = new LVPropertiesStack(m_vg); m_lv_properties_stack->setFrameStyle(m_vg_tree->frameStyle()); lv_splitter->addWidget(m_lv_properties_stack); QList pv_size_list; pv_size_list << 1500 << 10 ; pv_splitter->setSizes(pv_size_list); m_pv_properties_stack = new PVPropertiesStack(m_vg); m_pv_properties_stack->setFrameStyle(m_vg_tree->frameStyle()); pv_splitter->addWidget(m_pv_properties_stack); return; } void VolumeGroupTab::rescan() { disconnect(m_vg_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_lv_properties_stack, SLOT(changeLVStackIndex(QTreeWidgetItem*, QTreeWidgetItem*))); disconnect(m_pv_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_pv_properties_stack, SLOT(changePVStackIndex(QTreeWidgetItem*, QTreeWidgetItem*))); if (m_vg_info_labels) { m_layout->removeWidget(m_vg_info_labels); m_vg_info_labels->setParent(nullptr); m_vg_info_labels->deleteLater(); } m_vg_info_labels = new VGInfoLabels(m_vg); m_layout->insertWidget(1, m_vg_info_labels); m_lv_properties_stack->loadData(); m_pv_properties_stack->loadData(); connect(m_vg_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_lv_properties_stack, SLOT(changeLVStackIndex(QTreeWidgetItem*, QTreeWidgetItem*))); connect(m_pv_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_pv_properties_stack, SLOT(changePVStackIndex(QTreeWidgetItem*, QTreeWidgetItem*))); m_vg_warning->loadMessage(m_vg); m_vg_tree->loadData(); // This needs to be done after the lv property stack is built m_pv_tree->loadData(); // This needs to be done after the pv property stack is loaded if (m_lv_size_chart) { // This needs to be after the vgtree is loaded m_layout->removeWidget(m_lv_size_chart); m_lv_size_chart->setParent(nullptr); m_lv_size_chart->deleteLater(); } m_lv_size_chart = new LVSizeChart(m_vg, m_vg_tree); m_layout->insertWidget(2, m_lv_size_chart); connect(m_lv_size_chart, SIGNAL(lvMenuRequested(LogVol *)), this, SLOT(lvContextMenu(LogVol *))); readConfig(); return; } VolGroup* VolumeGroupTab::getVg() { return m_vg; } void VolumeGroupTab::readConfig() { KConfigSkeleton skeleton; bool show_vg_info, show_lv_bar, show_toolbars; QString icon_size, icon_text; skeleton.setCurrentGroup("General"); skeleton.addItemBool("show_vg_info", show_vg_info, true); skeleton.addItemBool("show_lv_bar", show_lv_bar, true); skeleton.addItemBool("show_toolbars", show_toolbars, true); skeleton.addItemString("toolbar_icon_size", icon_size, "mediumicons"); skeleton.addItemString("toolbar_icon_text", icon_text, "iconsonly"); if (show_vg_info) m_vg_info_labels->show(); else m_vg_info_labels->hide(); if (show_lv_bar) m_lv_size_chart->show(); else m_lv_size_chart->hide(); if (show_toolbars) { toolBar("lvtoolbar")->show(); toolBar("pvtoolbar")->show(); } else { toolBar("lvtoolbar")->hide(); toolBar("pvtoolbar")->hide(); } QSize size(22, 22); if (icon_size == "smallicons") size = QSize(16, 16); else if (icon_size == "mediumicons") size = QSize(22, 22); else if (icon_size == "largeicons") size = QSize(32, 32); else if (icon_size == "hugeicons") size = QSize(48, 48); toolBar("lvtoolbar")->setIconSize(size); toolBar("pvtoolbar")->setIconSize(size); if (icon_text == "iconsonly") { toolBar("lvtoolbar")->setToolButtonStyle(Qt::ToolButtonIconOnly); toolBar("pvtoolbar")->setToolButtonStyle(Qt::ToolButtonIconOnly); } else if (icon_text == "textonly") { toolBar("lvtoolbar")->setToolButtonStyle(Qt::ToolButtonTextOnly); toolBar("pvtoolbar")->setToolButtonStyle(Qt::ToolButtonTextOnly); } else if (icon_text == "textbesideicons") { toolBar("lvtoolbar")->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBar("pvtoolbar")->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else if (icon_text == "textundericons") { toolBar("lvtoolbar")->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); toolBar("pvtoolbar")->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } } KToolBar* VolumeGroupTab::buildLvToolBar() { KToolBar *const toolbar = toolBar("lvtoolbar"); toolbar->setContextMenuPolicy(Qt::PreventContextMenu); toolbar->addAction(m_lv_actions->action("lvcreate")); toolbar->addAction(m_lv_actions->action("thinpool")); toolbar->addAction(m_lv_actions->action("thincreate")); toolbar->addSeparator(); toolbar->addAction(m_lv_actions->action("lvremove")); toolbar->addSeparator(); toolbar->addAction(m_lv_actions->action("lvrename")); toolbar->addAction(m_lv_actions->action("snapcreate")); toolbar->addAction(m_lv_actions->action("thinsnap")); toolbar->addAction(m_lv_actions->action("snapmerge")); toolbar->addAction(m_lv_actions->action("lvreduce")); toolbar->addAction(m_lv_actions->action("lvextend")); toolbar->addAction(m_lv_actions->action("pvmove")); toolbar->addAction(m_lv_actions->action("lvchange")); toolbar->addSeparator(); toolbar->addAction(m_lv_actions->action("addlegs")); toolbar->addAction(m_lv_actions->action("changelog")); toolbar->addAction(m_lv_actions->action("removemirror")); toolbar->addAction(m_lv_actions->action("removethis")); toolbar->addAction(m_lv_actions->action("repairmissing")); toolbar->addAction(m_lv_actions->action("resync")); toolbar->addSeparator(); toolbar->addAction(m_lv_actions->action("mount")); toolbar->addAction(m_lv_actions->action("unmount")); toolbar->addAction(m_lv_actions->action("maxfs")); toolbar->addAction(m_lv_actions->action("fsck")); toolbar->addAction(m_lv_actions->action("mkfs")); return toolbar; } KToolBar* VolumeGroupTab::buildPvToolBar() { KToolBar *const toolbar = toolBar("pvtoolbar"); toolbar->setContextMenuPolicy(Qt::PreventContextMenu); KSeparator *const separator = new KSeparator(Qt::Vertical); separator->setMinimumWidth(40); toolbar->addWidget(separator); toolbar->addAction(m_pv_actions->action("pvmove")); toolbar->addAction(m_pv_actions->action("pvremove")); toolbar->addAction(m_pv_actions->action("pvchange")); return toolbar; } void VolumeGroupTab::lvContextMenu(LogVol *lv) { m_lv_actions->changeLv(lv, -1); QMenu *menu = new LVActionsMenu(m_lv_actions, this); menu->exec(QCursor::pos()); menu->deleteLater(); } void VolumeGroupTab::lvContextMenu(QTreeWidgetItem *item) { m_lv_actions->changeLv(item); QMenu *menu = new LVActionsMenu(m_lv_actions, this); menu->exec(QCursor::pos()); menu->deleteLater(); } void VolumeGroupTab::pvContextMenu(QTreeWidgetItem *item) { m_pv_actions->changePv(item); QMenu *menu = new PVActionsMenu(m_pv_actions, this); menu->exec(QCursor::pos()); menu->deleteLater(); } kvpm-0.9.10/kvpm/progressbox.cpp0000644000175000017500000000352412770324126017050 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "progressbox.h" #include #include #include #include #include ProgressBox::ProgressBox(QWidget *parent) : QFrame(parent) { QHBoxLayout *layout = new QHBoxLayout(); m_message = new QLabel(); m_message->setMinimumWidth(150); m_message->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_progressbar = new QProgressBar(); m_progressbar->setTextVisible(false); layout->setMargin(0); layout->addWidget(m_message); layout->addWidget(m_progressbar); setLayout(layout); } void ProgressBox::setText(const QString text) { hide(); if (!text.isEmpty()) m_message->setText(text + " >>"); else m_message->clear(); show(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } void ProgressBox::setRange(const int start, const int end) { hide(); m_progressbar->setRange(start, end); m_message->clear(); show(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } void ProgressBox::setValue(const int value) { hide(); m_progressbar->setValue(value); if (value >= m_progressbar->maximum()) m_message->clear(); show(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } void ProgressBox::reset() { hide(); m_progressbar->setRange(0, 1); m_progressbar->setValue(1); m_message->clear(); show(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } kvpm-0.9.10/kvpm/vginfolabels.h0000644000175000017500000000111712770324126016607 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGINFOLABELS_H #define VGINFOLABELS_H #include class VolGroup; class VGInfoLabels : public QFrame { public: explicit VGInfoLabels(VolGroup *const group, QWidget *parent = 0); }; #endif kvpm-0.9.10/kvpm/devicetree.cpp0000644000175000017500000004345612770324126016622 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "devicetree.h" #include #include #include #include #include "devicemenu.h" #include "devicepropertiesstack.h" #include "devicesizechart.h" #include "physvol.h" #include "storagedevice.h" #include "storagepartition.h" #include "volgroup.h" DeviceTree::DeviceTree(DeviceSizeChart *const chart, DevicePropertiesStack *const stack, QWidget *parent) : QTreeWidget(parent), m_chart(chart), m_stack(stack) { const QStringList headers = QStringList() << "Device" << "Type" << "Capacity" << "Remaining" << "Usage" << "Group" << "Flags" << "Mount point" ; QTreeWidgetItem *const item = new QTreeWidgetItem(static_cast(nullptr), headers); for (int column = 0; column < item->columnCount() ; column++) item->setTextAlignment(column, Qt::AlignCenter); item->setToolTip(0, i18n("The device name")); item->setToolTip(1, i18n("The type of partition or device")); item->setToolTip(2, i18n("The amount of storage space")); item->setToolTip(3, i18n("The remaining storage space")); item->setToolTip(4, i18n("How the device is being used")); item->setToolTip(5, i18n("The Name of the volume group if the device is a physical volume")); item->setToolTip(6, i18n("Any flags associated with device")); item->setToolTip(7, i18n("Mount points of the filesystem if it is mounted")); m_initial_run = true; setHeaderItem(item); setAlternatingRowColors(true); setAllColumnsShowFocus(true); setExpandsOnDoubleClick(true); setSelectionBehavior(QAbstractItemView::SelectRows); setupContextMenu(); } /* item->data(x, Qt::UserRole) x = 0: pointer to storagepartition if partition, else "" x = 1: pointer to storagedevice */ void DeviceTree::loadData(QList devices) { setSortingEnabled(false); disconnect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem *)), m_chart, SLOT(setNewDevice(QTreeWidgetItem*))); disconnect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_stack, SLOT(changeDeviceStackIndex(QTreeWidgetItem*))); setViewConfig(); QString current_device, current_parent; currentItemNames(current_device, current_parent); // Note - pass by reference, returns both args QStringList expanded_items, old_dev_names; expandedItemNames(expanded_items, old_dev_names); // Ditto for (auto dev : devices) { QTreeWidgetItem *const dev_item = buildDeviceItem(dev); addTopLevelItem(dev_item); for (auto const part : dev->getStoragePartitions()) { if (!part->isLogical() && !part->isLogicalFreespace()) { QTreeWidgetItem *const part_item = buildPartitionItem(part, dev); dev_item->addChild(part_item); if (part->isExtended()) expandItem(part_item, expanded_items, old_dev_names); } } expandItem(dev_item, expanded_items, old_dev_names); } connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem *)), m_chart, SLOT(setNewDevice(QTreeWidgetItem*))); connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_stack, SLOT(changeDeviceStackIndex(QTreeWidgetItem*))); restoreCurrentItem(current_device, current_parent); resizeColumnToContents(0); resizeColumnToContents(3); resizeColumnToContents(5); } void DeviceTree::setupContextMenu() { setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(popupContextMenu(QPoint))); } void DeviceTree::popupContextMenu(QPoint point) { emit deviceMenuRequested(itemAt(point)); } void DeviceTree::setViewConfig() { KConfigSkeleton skeleton; bool device, partition, capacity, remaining, usage, group, flags, mount; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", m_use_si_units, false); skeleton.setCurrentGroup("DeviceTreeColumns"); skeleton.addItemBool("dt_device", device, true); skeleton.addItemBool("dt_partition", partition, true); skeleton.addItemBool("dt_capacity", capacity, true); skeleton.addItemBool("dt_remaining", remaining, true); skeleton.addItemBool("dt_usage", usage, true); skeleton.addItemBool("dt_group", group, true); skeleton.addItemBool("dt_flags", flags, true); skeleton.addItemBool("dt_mount", mount, true); skeleton.addItemBool("dt_expandparts", m_expand_parts, true); skeleton.setCurrentGroup("AllTreeColumns"); skeleton.addItemBool("show_total", m_show_total, false); skeleton.addItemBool("show_percent", m_show_percent, false); skeleton.addItemBool("show_both", m_show_both, true); skeleton.addItemInt("fs_warn", m_fs_warn_percent, 10); skeleton.addItemInt("pv_warn", m_pv_warn_percent, 0); bool hidden_columns_changed = false; if (isColumnHidden(0) == device) { hidden_columns_changed = true; setColumnHidden(0, !device); } if (isColumnHidden(1) == partition) { hidden_columns_changed = true; setColumnHidden(1, !partition); } if (isColumnHidden(2) == capacity) { hidden_columns_changed = true; setColumnHidden(2, !capacity); } if (isColumnHidden(3) == remaining) { hidden_columns_changed = true; setColumnHidden(3, !remaining); } if (isColumnHidden(4) == usage) { hidden_columns_changed = true; setColumnHidden(4, !usage); } if (isColumnHidden(5) == group) { hidden_columns_changed = true; setColumnHidden(5, !group); } if (isColumnHidden(6) == flags) { hidden_columns_changed = true; setColumnHidden(6, !flags); } if (isColumnHidden(7) == mount) { hidden_columns_changed = true; setColumnHidden(7, !mount); } bool skip = true; if (hidden_columns_changed) { for (int i = 7; i >= 0; --i) { if (!isColumnHidden(i)) { if (skip) { skip = false; } else { resizeColumnToContents(i); } } } } } QTreeWidgetItem *DeviceTree::buildDeviceItem(StorageDevice *const dev) { QTreeWidgetItem *const item = new QTreeWidgetItem(getDeviceItemData(dev)); QVariant dev_variant; dev_variant.setValue(static_cast(dev)); item->setData(1, Qt::UserRole, dev_variant); item->setTextAlignment(2, Qt::AlignRight); item->setTextAlignment(3, Qt::AlignRight); setItemAttributes(item, dev); return item; } QTreeWidgetItem *DeviceTree::buildPartitionItem(StoragePartition *const part, StorageDevice *const dev) { QTreeWidgetItem *const item = new QTreeWidgetItem(getPartitionItemData(part)); QVariant part_variant, dev_variant; part_variant.setValue(static_cast(part)); dev_variant.setValue(static_cast(dev)); item->setData(0, Qt::UserRole, part_variant); item->setData(1, Qt::UserRole, dev_variant); item->setTextAlignment(2, Qt::AlignRight); item->setTextAlignment(3, Qt::AlignRight); setItemAttributes(item, part); if (part->isExtended()) { for (auto part : dev->getStoragePartitions()) { if (part->isLogical() || part->isLogicalFreespace()) item->addChild(buildPartitionItem(part, dev)); } } return item; } QStringList DeviceTree::getDeviceItemData(const StorageDevice *const dev) { KFormat::BinaryUnitDialect dialect; if (m_use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; QString external_raid; if (dev->isDmRaid()) external_raid = "dm volume"; else if (dev->isMdRaid()) external_raid = "md volume"; QStringList data = QStringList() << dev->getName() << external_raid; if (dev->isPhysicalVolume()) { data << KFormat().formatByteSize(dev->getSize(), 1, dialect); const PhysVol *const pv = dev->getPhysicalVolume(); if (m_show_total && !m_show_percent) data << KFormat().formatByteSize(pv->getRemaining(), 1, dialect); else if (!m_show_total && m_show_percent) data << QString("%%1").arg(100 - pv->getPercentUsed()); else if (m_show_both) data << QString("%1 (%%2) ").arg(KFormat().formatByteSize(pv->getRemaining(), 1, dialect)).arg(100 - pv->getPercentUsed()); data << "PV" << pv->getVg()->getName(); } else { data << KFormat().formatByteSize(dev->getSize(), 1, dialect) << ""; if(dev->isDmBlock()) data << "dm device"; else if(dev->isMdBlock()) data << "md device"; } return data; } QStringList DeviceTree::getPartitionItemData(const StoragePartition *const part) { KFormat::BinaryUnitDialect dialect; if (m_use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; QStringList data = QStringList() << part->getName() << part->getType() << KFormat().formatByteSize(part->getSize(), 1, dialect); if (part->isPhysicalVolume()) { PhysVol *const pv = part->getPhysicalVolume(); if (m_show_total && !m_show_percent) data << KFormat().formatByteSize(pv->getRemaining(), 1, dialect); else if (!m_show_total && m_show_percent) data << QString("%%1").arg(100 - pv->getPercentUsed()); else data << QString("%1 (%%2) ").arg(KFormat().formatByteSize(pv->getRemaining(), 1, dialect)).arg(100 - pv->getPercentUsed()); data << "PV" << pv->getVg()->getName() << (part->getFlags()).join(", ") << ""; } else { if (part->getFilesystemSize() > -1 && part->getFilesystemUsed() > -1) { if (m_show_total && !m_show_percent) data << KFormat().formatByteSize(part->getFilesystemRemaining(), 1, dialect); else if (!m_show_total && m_show_percent) data << QString("%%1").arg(100 - part->getFilesystemPercentUsed()); else data << QString("%1 (%%2) ").arg(KFormat().formatByteSize(part->getFilesystemRemaining(), 1, dialect)).arg(100 - part->getFilesystemPercentUsed()); } else { data << ""; } if(part->isMdBlock()) data << "md device"; else data << part->getFilesystem(); data << "" << (part->getFlags()).join(", "); if (part->isMounted()) data << (part->getMountPoints())[0]; else if (part->isBusy() && (part->getFilesystem() == "swap")) data << "swapping"; else data << ""; } return data; } // Re-sets the current item back to what it was, if possible. // If the old current item is gone it looks for the parent of the // current item next. void DeviceTree::restoreCurrentItem(const QString current, const QString currentParent) { bool match = false; if (m_initial_run) { m_initial_run = false; if (m_expand_parts) expandAll(); } else { for (int i = topLevelItemCount() - 1; i >= 0; --i) { QTreeWidgetItem *const parent = topLevelItem(i); if (parent->data(0, Qt::DisplayRole).toString() == current) { match = true; setCurrentItem(parent); break; } for (int j = parent->childCount() - 1; j >= 0; --j) { QTreeWidgetItem *const child = parent->child(j); if (child->data(0, Qt::DisplayRole).toString() == current) { match = true; setCurrentItem(child); break; } for (int k = child->childCount() - 1; k >= 0; --k) { if (child->child(k)->data(0, Qt::DisplayRole).toString() == current) { match = true; setCurrentItem(child->child(k)); break; } } if ((child->data(0, Qt::DisplayRole).toString() == currentParent) && (!match)) { match = true; setCurrentItem(child); break; } if (match) break; } if ((parent->data(0, Qt::DisplayRole).toString() == currentParent) && (!match)) { match = true; setCurrentItem(parent); break; } if (match) break; } } if (topLevelItemCount() && (currentItem() == nullptr)) setCurrentItem(topLevelItem(0)); } // Gets the name of the current item (the highlighted one) in the tree // and the name of its parent. void DeviceTree::currentItemNames(QString ¤t, QString ¤tParent) { if (currentItem()){ current = currentItem()->data(0, Qt::DisplayRole).toString(); if (currentItem()->parent()) currentParent = currentItem()->parent()->data(0, Qt::DisplayRole).toString(); else currentParent = current; } else { current.clear(); currentParent.clear(); } } // Gets the names of just the expanded items and the names of all items // in the tree before everything gets deleted. void DeviceTree::expandedItemNames(QStringList &expanded, QStringList &old) { expanded.clear(); old.clear(); for (int i = topLevelItemCount() - 1; i >= 0; --i) { auto parent = topLevelItem(i); if (parent->isExpanded()) expanded << parent->data(0, Qt::DisplayRole).toString(); for (int j = parent->childCount() - 1; j >= 0; --j) { auto child = parent->child(j); if (child->isExpanded()) expanded << child->data(0, Qt::DisplayRole).toString(); old << child->data(0, Qt::DisplayRole).toString(); } old << parent->data(0, Qt::DisplayRole).toString(); delete takeTopLevelItem(i); } } // If the old item for this dev was expanded, expand new one. Also // expand it if it didn't exsist before and m_expand_part config is set. void DeviceTree::expandItem(QTreeWidgetItem *const item, const QStringList expanded, const QStringList old) { const QString name = item->data(0, Qt::DisplayRole).toString(); if (old.contains(name)) { if(expanded.contains(name)) item->setExpanded(true); else item->setExpanded(false); } else { item->setExpanded(m_expand_parts); } } void DeviceTree::setItemAttributes(QTreeWidgetItem *const item, const StorageBase *const devbase) { if (devbase->isDmRaid()) item->setToolTip(1, i18n("A device mapper RAID volume")); else if (devbase->isMdRaid()) item->setToolTip(1, i18n("A multiple device RAID volume")); if (devbase->isDmBlock()) { item->setToolTip(4, i18n("A block device under a device mapper RAID volume")); } else if (devbase->isMdBlock()) { item->setToolTip(4, i18n("A block device under a multiple device RAID volume")); } if (devbase->isPhysicalVolume()) { if (m_pv_warn_percent && (m_pv_warn_percent >= (100 - devbase->getPhysicalVolume()->getPercentUsed()))) { item->setIcon(3, QIcon::fromTheme(QStringLiteral("dialog-warning"))); item->setToolTip(3, i18n("Physical volume that is running out of space")); } if (devbase->getPhysicalVolume()->isActive()) { item->setIcon(4, QIcon::fromTheme(QStringLiteral("lightbulb"))); item->setToolTip(4, i18n("Physical volume with active logical volumes")); } else { item->setIcon(4, QIcon::fromTheme(QStringLiteral("lightbulb_off"))); item->setToolTip(4, i18n("Physical volume without active logical volumes")); } } const StoragePartition *const part = dynamic_cast(devbase); if(part) { // will be null pointer here if it is really a device, not a partition. if (part->isMountable()) { if (part->isMounted()) { if (m_fs_warn_percent && (m_fs_warn_percent >= (100 - part->getFilesystemPercentUsed()))) { item->setIcon(3, QIcon::fromTheme(QStringLiteral("dialog-warning"))); item->setToolTip(3, i18n("Filesystem that is running out of space")); } item->setIcon(4, QIcon::fromTheme(QStringLiteral("emblem-mounted"))); item->setToolTip(4, i18n("mounted filesystem")); } else { item->setIcon(4, QIcon::fromTheme(QStringLiteral("emblem-unmounted"))); item->setToolTip(4, i18n("unmounted filesystem")); } } else if (part->getFilesystem() == "swap") { if (part->isBusy()) { item->setIcon(4, QIcon::fromTheme(QStringLiteral("task-recurring"))); item->setToolTip(4, i18n("Active swap area")); } else { item->setIcon(4, QIcon::fromTheme(QStringLiteral("emblem-unmounted"))); item->setToolTip(4, i18n("Inactive swap area")); } } } } kvpm-0.9.10/kvpm/lvchange.cpp0000644000175000017500000003001612770324126016256 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvchange.h" #include "allocationpolicy.h" #include "logvol.h" #include "masterlist.h" #include "processprogress.h" #include "volgroup.h" #include #include #include #include #include #include #include #include #include LVChangeDialog::LVChangeDialog(LogVol *const volume, QWidget *parent) : KvpmDialog(parent), m_lv(volume) { setCaption(i18n("Change Logical Volume Attributes")); QWidget *const dialog_body = new QWidget(); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout; QLabel *const name = new QLabel(i18n("Change volume: %1", m_lv->getName())); name->setAlignment(Qt::AlignCenter); layout->addWidget(name); layout->addSpacing(10); layout->addStretch(); QTabWidget *const tab_widget = new QTabWidget(); layout->addWidget(tab_widget); tab_widget->addTab(buildGeneralTab(), i18nc("The standard or basic options", "General")); tab_widget->addTab(buildAdvancedTab(), i18nc("Less used or complex options", "Advanced")); layout->addStretch(); dialog_body->setLayout(layout); connect(m_available_check, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_ro_check, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_refresh_check, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_udevsync_check, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_persistent_check, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_deltag_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(resetOkButton())); connect(m_tag_edit, SIGNAL(textChanged(QString)), this, SLOT(resetOkButton())); connect(m_polling_box, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_poll_button, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_nopoll_button, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_devnum_box, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_available_check, SIGNAL(stateChanged(int)), this , SLOT(refreshAndAvailableCheck())); connect(m_refresh_check, SIGNAL(stateChanged(int)), this , SLOT(refreshAndAvailableCheck())); resetOkButton(); } QWidget *LVChangeDialog::buildGeneralTab() { QWidget *const tab = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); tab->setLayout(layout); QGroupBox *const general_group = new QGroupBox(); QVBoxLayout *const general_layout = new QVBoxLayout(); general_group->setLayout(general_layout); layout->addWidget(general_group); m_available_check = new QCheckBox(i18n("Make volume available (active)")); m_ro_check = new QCheckBox(i18n("Make volume read only")); m_refresh_check = new QCheckBox(i18n("Refresh volume metadata")); general_layout->addWidget(m_available_check); general_layout->addWidget(m_ro_check); general_layout->addWidget(m_refresh_check); m_available_check->setChecked(m_lv->isActive()); m_ro_check->setChecked(!m_lv->isWritable()); if (m_lv->isMounted() || m_lv->isCowSnap()) m_available_check->setEnabled(false); QGroupBox *const tag_group = new QGroupBox(i18n("Volume Tags")); layout->addWidget(tag_group); QHBoxLayout *const add_tag_layout = new QHBoxLayout(); QHBoxLayout *const del_tag_layout = new QHBoxLayout(); QVBoxLayout *const tag_group_layout = new QVBoxLayout(); tag_group_layout->addLayout(add_tag_layout); tag_group_layout->addLayout(del_tag_layout); tag_group->setLayout(tag_group_layout); QLabel *const add_tag_label = new QLabel(i18n("Add new tag:")); add_tag_layout->addWidget(add_tag_label); m_tag_edit = new QLineEdit(); add_tag_label->setBuddy(m_tag_edit); QRegExp rx("[0-9a-zA-Z_\\.+-]*"); QRegExpValidator *tag_validator = new QRegExpValidator(rx, m_tag_edit); m_tag_edit->setValidator(tag_validator); add_tag_layout->addWidget(m_tag_edit); QLabel *const del_tag_label = new QLabel(i18n("Remove tag:")); del_tag_layout->addWidget(del_tag_label); m_deltag_combo = new QComboBox(); del_tag_label->setBuddy(m_deltag_combo); m_deltag_combo->setEditable(false); QStringList tags = m_lv->getTags(); for (int x = 0; x < tags.size(); x++) m_deltag_combo->addItem(tags[x]); m_deltag_combo->insertItem(0, QString("")); m_deltag_combo->setCurrentIndex(0); del_tag_layout->addWidget(m_deltag_combo); if (!m_lv->isThinVolume()) { m_policy_combo = new PolicyComboBox(m_lv->getPolicy(), m_lv->getVg()->getPolicy()); general_layout->addWidget(m_policy_combo); connect(m_policy_combo, SIGNAL(policyChanged(AllocationPolicy)), this, SLOT(resetOkButton())); } else { m_policy_combo = nullptr; } return tab; } QWidget *LVChangeDialog::buildAdvancedTab() { QWidget *const tab = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); tab->setLayout(layout); QGroupBox *const sync_box = new QGroupBox(); layout->addWidget(sync_box); QVBoxLayout *const sync_layout = new QVBoxLayout(); sync_box->setLayout(sync_layout); m_udevsync_check = new QCheckBox(i18n("Synchronize with udev")); m_udevsync_check->setChecked(true); sync_layout->addWidget(m_udevsync_check); m_polling_box = new QGroupBox(i18n("Change Volume Polling")); m_polling_box->setCheckable(true); m_polling_box->setChecked(false); layout->addWidget(m_polling_box); QVBoxLayout *const poll_layout = new QVBoxLayout(); m_polling_box->setLayout(poll_layout); m_poll_button = new QRadioButton(i18n("Start polling")); m_poll_button->setChecked(true); poll_layout->addWidget(m_poll_button); m_nopoll_button = new QRadioButton(i18n("Stop polling")); poll_layout->addWidget(m_nopoll_button); m_dmeventd_box = new QGroupBox(i18n("Change dmeventd Monitoring")); m_dmeventd_box->setCheckable(true); m_dmeventd_box->setChecked(false); QVBoxLayout *const mirror_layout = new QVBoxLayout(); m_dmeventd_box->setLayout(mirror_layout); m_monitor_button = new QRadioButton(i18n("Monitor with dmeventd")); m_nomonitor_button = new QRadioButton(i18n("Do not monitor")); m_ignore_button = new QRadioButton(i18n("Ignore dmeventd")); m_monitor_button->setChecked(true); mirror_layout->addWidget(m_monitor_button); mirror_layout->addWidget(m_nomonitor_button); mirror_layout->addWidget(m_ignore_button); layout->addWidget(m_dmeventd_box); if (m_lv->isCowSnap() || m_lv->isMirror() || m_lv->isRaid()) { connect(m_dmeventd_box, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_monitor_button, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_nomonitor_button, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_ignore_button, SIGNAL(clicked()), this, SLOT(resetOkButton())); } else { m_dmeventd_box->setEnabled(false); m_dmeventd_box->hide(); } layout->addStretch(); m_devnum_box = new QGroupBox(i18n("Change Kernel Device Numbers")); m_devnum_box->setCheckable(true); QVBoxLayout *const devnum_layout = new QVBoxLayout(); m_devnum_box->setLayout(devnum_layout); QHBoxLayout *const major_layout = new QHBoxLayout(); QHBoxLayout *const minor_layout = new QHBoxLayout(); m_persistent_check = new QCheckBox(i18n("Use persistent device numbers")); devnum_layout->addWidget(m_persistent_check); devnum_layout->addLayout(major_layout); devnum_layout->addLayout(minor_layout); m_major_edit = new QLineEdit(QString("%1").arg(m_lv->getMajorDevice())); QLabel *const major_label = new QLabel(i18n("Major number: ")); major_label->setBuddy(m_major_edit); major_layout->addWidget(major_label); major_layout->addWidget(m_major_edit); m_minor_edit = new QLineEdit(QString("%1").arg(m_lv->getMinorDevice())); QLabel *const minor_label = new QLabel(i18n("Minor number: ")); minor_label->setBuddy(m_minor_edit); minor_layout->addWidget(minor_label); minor_layout->addWidget(m_minor_edit); layout->addWidget(m_devnum_box); m_persistent_check->setChecked(m_lv->isPersistent()); m_devnum_box->setChecked(false); return tab; } QStringList LVChangeDialog::arguments() { QStringList args, temp; args << "lvchange" << "--yes"; // answer yes to any question if (!m_lv->isCowSnap()) { if (m_available_check->isChecked() && (!m_lv->isActive())) { args << "--available" << "y"; if ( MasterList::isLvmVersionEqualOrGreater("2.02.109") ) args << "-K"; } else if ((!m_available_check->isChecked()) && (m_lv->isActive())) { args << "--available" << "n"; } } if (m_ro_check->isChecked() && m_lv->isWritable()) args << "--permission" << "r"; else if ((!m_ro_check->isChecked()) && (!m_lv->isWritable())) args << "--permission" << "rw"; if (m_refresh_check->isChecked()) args << "--refresh"; if (m_lv->isCowSnap() || m_lv->isMirror() || m_lv->isRaid()) { if (m_dmeventd_box->isChecked()) { if (m_monitor_button->isChecked()) args << "--monitor" << "y"; else if (m_nomonitor_button->isChecked()) args << "--monitor" << "n"; else args << "--ignoremonitoring"; } } if (m_devnum_box->isChecked()) { if (m_persistent_check->isChecked()) { args << "--force" << "--persistent" << "y"; args << "--major" << m_major_edit->text(); args << "--minor" << m_minor_edit->text(); } else { args << "--force" << "--persistent" << "n"; args << "--major" << m_major_edit->text(); args << "--minor" << m_minor_edit->text(); } } if (m_polling_box->isChecked()) { if (m_poll_button->isChecked()) args << "--poll" << "y"; else args << "--poll" << "n"; } if (!m_udevsync_check->isChecked()) args << "--noudevsync"; if (m_deltag_combo->currentIndex()) args << "--deltag" << m_deltag_combo->currentText(); if (!(m_tag_edit->text()).isEmpty()) args << "--addtag" << m_tag_edit->text(); if (m_policy_combo) { if (m_lv->getPolicy() != m_policy_combo->getPolicy()) args << "--alloc" << policyToString(m_policy_combo->getPolicy()); } args << m_lv->getFullName(); return args; } void LVChangeDialog::commit() { hide(); ProcessProgress change_lv(arguments()); } void LVChangeDialog::resetOkButton() { if (m_available_check->isChecked()) { m_polling_box->setEnabled(true); } else { m_polling_box->setEnabled(false); m_polling_box->setChecked(false); } QStringList args = arguments(); args.removeAt(args.indexOf(QString("--ignoremonitoring"))); if (args.size() > 3) enableButtonOk(true); else enableButtonOk(false); } // metadata refresh and availability change can't happen at the same time void LVChangeDialog::refreshAndAvailableCheck() { if (!m_lv->isMounted() && !m_lv->isCowSnap()) { if (m_lv->isActive() == m_available_check->isChecked()) { m_refresh_check->setEnabled(true); } else { m_refresh_check->setEnabled(false); m_refresh_check->setChecked(false); } if (m_refresh_check->isChecked()) { m_available_check->setChecked(m_lv->isActive()); m_available_check->setEnabled(false); } else { m_available_check->setEnabled(true); } } } kvpm-0.9.10/kvpm/volgroup.cpp0000644000175000017500000003105312770324126016346 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "volgroup.h" #include #include "mounttables.h" #include "physvol.h" VolGroup::VolGroup(lvm_t lvm, const char *vgname, MountTables *const tables) { m_vg_name = QString(vgname).trimmed(); m_tables = tables; rescan(lvm); } VolGroup::~VolGroup() { for (auto ptr : m_member_pvs) delete ptr; } void VolGroup::rescan(lvm_t lvm) { vg_t lvm_vg; lvm_property_value value; m_allocatable_extents = 0; m_mda_count = 0; m_mda_used = 0; m_pv_max = 0; m_extent_size = 0; m_extents = 0; m_lv_max = 0; m_size = 0; m_free = 0; m_free_extents = 0; m_exported = false; m_partial = false; m_clustered = false; m_active = false; m_open_failed = false; // clustered volumes can't be opened when clvmd isn't running QByteArray vg_name_qba = m_vg_name.toLocal8Bit(); const char *vg_name_ascii = vg_name_qba.data(); if ((lvm_vg = lvm_vg_open(lvm, vg_name_ascii, "r", 0x00))) { m_pv_max = lvm_vg_get_max_pv(lvm_vg); m_extent_size = lvm_vg_get_extent_size(lvm_vg); m_extents = lvm_vg_get_extent_count(lvm_vg); m_lv_max = lvm_vg_get_max_lv(lvm_vg); m_size = lvm_vg_get_size(lvm_vg); m_free = lvm_vg_get_free_size(lvm_vg); m_free_extents = lvm_vg_get_free_extent_count(lvm_vg); m_exported = (bool)lvm_vg_is_exported(lvm_vg); m_partial = (bool)lvm_vg_is_partial(lvm_vg); m_clustered = (bool)lvm_vg_is_clustered(lvm_vg); m_uuid = QString(lvm_vg_get_uuid(lvm_vg)); value = lvm_vg_get_property(lvm_vg, "vg_fmt"); m_lvm_format = QString(value.value.string); value = lvm_vg_get_property(lvm_vg, "vg_attr"); const QString vg_attr = QString(value.value.string); if (vg_attr.at(0) == 'w') m_writable = true; else m_writable = false; if (vg_attr.at(1) == 'z') m_resizable = true; else m_resizable = false; if (vg_attr.at(4) == 'c') m_policy = CONTIGUOUS; else if (vg_attr.at(4) == 'l') m_policy = CLING; else if (vg_attr.at(4) == 'a') m_policy = ANYWHERE; else m_policy = NORMAL; processPhysicalVolumes(lvm_vg); processLogicalVolumes(lvm_vg); lvm_vg_close(lvm_vg); } else { if (QString(lvm_errmsg(lvm)).contains("cluster")) m_clustered = true; m_open_failed = true; for (int x = m_member_pvs.size() - 1; x >= 0; x--) delete m_member_pvs.takeAt(x); m_member_pvs.clear(); m_member_lvs.clear(); } setActivePhysicalVolumes(); setLastUsedExtent(); return; } /* Takes orphan volumes off the child lv list and puts them back onto the top level lv list. An orphan is a volume that should be hidden under another, like a mirror leg, but doesn't seem to have a parent, or something left behind by a misbehaving lvm process */ lv_t VolGroup::findOrphan(QList &childList) { LvList all_lvs = getLogicalVolumesFlat(); QList orphan_list; lvm_property_value value; QString child_name; QString lv_attr; lv_t orphan = nullptr; for (int x = all_lvs.size() - 1; x >= 0; x--) { for (int y = childList.size() - 1; y >= 0; y--) { value = lvm_lv_get_property(childList[y], "lv_name"); child_name = QString(value.value.string).trimmed(); if (child_name == all_lvs[x]->getName()) { childList.removeAt(y); all_lvs.removeAt(x); break; } } } for (const auto &child : childList) { // sort mirrors first, loners last value = lvm_lv_get_property(child, "lv_attr"); lv_attr = QString(value.value.string); if (lv_attr[0] == 'm' || lv_attr[0] == 'M') orphan_list.prepend(child); else orphan_list.append(child); } if (!orphan_list.isEmpty()) { orphan = orphan_list.takeAt(0); childList = orphan_list; return orphan; } childList = orphan_list; return nullptr; } LvList VolGroup::getLogicalVolumes() const // *TOP LEVEL ONLY* snapcontainers returned not snaps and origin { LvList members; for (auto lv : m_member_lvs) members << lv.data(); return members; } LvList VolGroup::getLogicalVolumesFlat() const { LvList flat_list; for (auto lv : m_member_lvs) { flat_list << lv.data(); flat_list << lv->getAllChildrenFlat(); } return flat_list; } LogVol * VolGroup::getLvByName(QString shortName) const // Do not return snap container, just the "real" lv { QListIterator lvs_itr(getLogicalVolumesFlat()); shortName = shortName.trimmed(); while (lvs_itr.hasNext()) { LogVol *lv = lvs_itr.next(); if (shortName == lv->getName() && !lv->isSnapContainer()) return lv; } return nullptr; } LogVol * VolGroup::getLvByUuid(QString uuid) const // Do not return snap container, just the "real" lv { QListIterator lvs_itr(getLogicalVolumesFlat()); uuid = uuid.trimmed(); while (lvs_itr.hasNext()) { LogVol *lv = lvs_itr.next(); if (uuid == lv->getUuid() && !lv->isSnapContainer()) return lv; } return nullptr; } PhysVol* VolGroup::getPvByName(QString name) const { QListIterator pvs_itr(m_member_pvs); name = name.trimmed(); if (!name.contains("unknown device")) { while (pvs_itr.hasNext()) { PhysVol *pv = pvs_itr.next(); if (name == pv->getMapperName()) return pv; } } return nullptr; } QStringList VolGroup::getLvNames() const { QStringList names; for (const auto lv : m_member_lvs) names << lv->getName(); return names; } void VolGroup::processPhysicalVolumes(vg_t lvmVG) { dm_list* pv_dm_list = lvm_vg_list_pvs(lvmVG); lvm_pv_list *pv_list; if (pv_dm_list) { // This should never be empty dm_list_iterate_items(pv_list, pv_dm_list) { // rescan() existing PhysVols bool existing_pv = false; for (int x = 0; x < m_member_pvs.size(); x++) { if (QString(lvm_pv_get_uuid(pv_list->pv)).trimmed() == m_member_pvs[x]->getUuid()) { existing_pv = true; m_member_pvs[x]->rescan(pv_list->pv); } } if (!existing_pv) m_member_pvs.append(new PhysVol(pv_list->pv, this)); } for (int x = m_member_pvs.size() - 1; x >= 0; x--) { // delete PhysVolGroup if the pv is gone bool deleted_pv = true; dm_list_iterate_items(pv_list, pv_dm_list) { if (QString(lvm_pv_get_uuid(pv_list->pv)).trimmed() == m_member_pvs[x]->getUuid()) deleted_pv = false; } if (deleted_pv) delete m_member_pvs.takeAt(x); } for (int x = 0; x < m_member_pvs.size(); x++) { if (m_member_pvs[x]->isAllocatable()) m_allocatable_extents += m_member_pvs[x]->getRemaining() / m_extent_size; m_mda_count += m_member_pvs[x]->getMdaCount(); m_mda_used += m_member_pvs[x]->getMdaUsed(); } } else { qDebug() << " Empty pv_dm_list?"; } } void VolGroup::processLogicalVolumes(vg_t lvmVG) { lvm_property_value value; dm_list* lv_dm_list = lvm_vg_list_lvs(lvmVG); lvm_lv_list *lv_list; QList lvm_lvs_all_top; // top level lvm logical volume handles QList lvm_lvs_all_children; // children of top level volumes QByteArray flags; QList old_member_lvs = m_member_lvs; m_member_lvs.clear(); m_lv_names_all.clear(); if (lv_dm_list) { dm_list_iterate_items(lv_list, lv_dm_list) { value = lvm_lv_get_property(lv_list->lv, "lv_name"); if (QString(value.value.string).trimmed().startsWith("snapshot")) continue; value = lvm_lv_get_property(lv_list->lv, "lv_name"); const QString top_name = QString(value.value.string).trimmed(); m_lv_names_all << top_name; if (top_name.contains("_mlog") || top_name.contains("_mimage") || top_name.contains("_tmeta") || top_name.contains("_rmeta") || top_name.contains("_tdata") || top_name.contains("_rimage")) { lvm_lvs_all_children.append(lv_list->lv); } else { value = lvm_lv_get_property(lv_list->lv, "lv_attr"); flags = value.value.string; switch (flags[0]) { case '-': case 'p': case 't': lvm_lvs_all_top.append(lv_list->lv); break; case 'c': case 'M': case 'm': case 'r': case 'O': case 'o': if (flags[6] == 't') lvm_lvs_all_children.append(lv_list->lv); else lvm_lvs_all_top.append(lv_list->lv); break; case 'V': case 'e': default: lvm_lvs_all_children.append(lv_list->lv); break; } } } for (auto lvm_lv : lvm_lvs_all_top) { // rescan() existing LogVols bool is_new = true; for (int x = old_member_lvs.size() - 1; x >= 0; x--) { value = lvm_lv_get_property(lvm_lv, "lv_attr"); flags = value.value.string; if (QString(lvm_lv_get_name(lvm_lv)).trimmed() == old_member_lvs[x]->getName()) { old_member_lvs[x]->rescan(lvm_lv, lvmVG); m_member_lvs.append(old_member_lvs.takeAt(x)); is_new = false; } } if (is_new) { m_member_lvs << SmrtLvPtr(new LogVol(lvm_lv, lvmVG, this, nullptr, m_tables)); } } old_member_lvs.clear(); lv_t lvm_lv_orphan; // non-top lvm logical volume handle with no home while ((lvm_lv_orphan = findOrphan(lvm_lvs_all_children))) m_member_lvs << SmrtLvPtr(new LogVol(lvm_lv_orphan, lvmVG, this, nullptr, m_tables, true)); } else { // lv_dm_list is empty so clean up member lvs m_member_lvs.clear(); } } void VolGroup::setActivePhysicalVolumes() { PhysVol *pv; const LvList all_lvs = getLogicalVolumesFlat(); for (int x = all_lvs.size() - 1; x >= 0; x--) { if (all_lvs[x]->isActive()) { m_active = true; const QStringList pv_name_list = all_lvs[x]->getPvNamesAllFlat(); for (int x = pv_name_list.size() - 1; x >= 0; x--) { if ((pv = getPvByName(pv_name_list[x]))) pv->setActive(); } } } } // TODO -- this should use pvseg properties pvseg_start pvseg_size // It should be moved to PhysVol void VolGroup::setLastUsedExtent() { for (auto pv : m_member_pvs) { long long last_extent = 0; long long last_used_extent = 0; const QString pv_name = pv->getMapperName(); for (auto smart : m_member_lvs) { auto lv = smart.data(); for (int segment = lv->getSegmentCount() - 1; segment >= 0; segment--) { QList starting_extent = lv->getSegmentStartingExtent(segment); const QStringList pv_name_list = lv->getPvNames(segment); for (int i = 0; i < pv_name_list.size(); i++) { if (pv_name == pv_name_list[i]) { last_extent = starting_extent[i] - 1 + (lv->getSegmentExtents(segment) / (lv->getSegmentStripes(segment))); if (last_extent > last_used_extent) last_used_extent = last_extent; } } } } pv->setLastUsedExtent(last_used_extent); } } kvpm-0.9.10/kvpm/lvpropertiesstack.cpp0000644000175000017500000001164312770324126020260 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvpropertiesstack.h" #include "lvproperties.h" #include "logvol.h" #include "volgroup.h" #include #include #include #include #include #include #include #include #include /* Each logical volume gets a stack of widgets. The widgets display information about the volume highlighted on the tree widget. One widget for each line (segment) on the tree. The stacks are in turn loaded onto a stack for the whole group. */ LVPropertiesStack::LVPropertiesStack(VolGroup *Group, QWidget *parent) : QFrame(parent), m_vg(Group) { m_vscroll = new QScrollArea; m_stack_widget = NULL; QVBoxLayout *const vlayout = new QVBoxLayout(); QHBoxLayout *const hlayout = new QHBoxLayout(); vlayout->setMargin(0); vlayout->setSpacing(0); m_lv_label = new QLabel; m_lv_label->setAlignment(Qt::AlignCenter); vlayout->addSpacing(2); vlayout->addWidget(m_lv_label); vlayout->addSpacing(2); vlayout->addWidget(m_vscroll); vlayout->addLayout(hlayout); setLayout(vlayout); m_vscroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_vscroll->setBackgroundRole(QPalette::Base); m_vscroll->setAutoFillBackground(true); m_vscroll->verticalScrollBar()->setBackgroundRole(QPalette::Window); m_vscroll->verticalScrollBar()->setAutoFillBackground(true); m_vscroll->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); m_vscroll->setWidgetResizable(true); m_vscroll->setFrameShape(QFrame::NoFrame); } /* If *item points to a logical volume we set the widget stack to the widget with that volume's information. Else we set the stack widget index to -1, nothing */ void LVPropertiesStack::changeLVStackIndex(QTreeWidgetItem *item, QTreeWidgetItem*) { QString lv_name; const LvList members = m_vg->getLogicalVolumesFlat(); if (item && (members.size() == m_lv_stack_list.size())) { // These *should* be equal const QString lv_uuid = QVariant(item->data(2, Qt::UserRole)).toString(); for (int x = members.size() - 1; x >= 0; --x) { if (lv_uuid == (members[x])->getUuid()) { m_stack_widget->setCurrentIndex(x); const int segment = QVariant(item->data(1, Qt::UserRole)).toInt(); if (segment == -1) { m_lv_stack_list[x]->setCurrentIndex(0); lv_name = item->data(0, Qt::UserRole).toString(); } else { m_lv_stack_list[x]->setCurrentIndex(segment + 1); if (item->data(3, Qt::UserRole).toString() == "segment") lv_name = QString("%1 (%2 %3)").arg(item->data(0, Qt::UserRole).toString()).arg(i18n("segment")).arg(segment); else lv_name = item->data(0, Qt::UserRole).toString(); } if (item->data(4, Qt::UserRole).toBool()) m_lv_label->setText("" + lv_name + QString(" + %1").arg(i18n("Snapshots")) + ""); else m_lv_label->setText("" + lv_name + ""); } } } else { m_stack_widget->setCurrentIndex(-1); m_lv_label->setText(""); } } void LVPropertiesStack::loadData() { const LvList members = m_vg->getLogicalVolumesFlat(); if (m_stack_widget == NULL) m_stack_widget = new QStackedWidget; for (int x = m_stack_widget->count() - 1; x >= 0; --x) { QWidget *widget = m_stack_widget->widget(x); m_stack_widget->removeWidget(widget); widget->deleteLater(); } m_lv_stack_list.clear(); for (int x = 0; x < members.size(); ++x) { QStackedWidget *const segment_properties_stack = new QStackedWidget(); if (members[x]->getSegmentCount() > 1) { segment_properties_stack->addWidget(new LVProperties(members[x], -1)); for (int segment = 0; segment < members[x]->getSegmentCount(); segment++) segment_properties_stack->addWidget(new LVProperties(members[x], segment)); } else segment_properties_stack->addWidget(new LVProperties(members[x], 0)); m_lv_stack_list.append(segment_properties_stack); m_stack_widget->addWidget(segment_properties_stack); } if (members.size()) m_stack_widget->setCurrentIndex(0); if (m_vscroll->widget() == 0) m_vscroll->setWidget(m_stack_widget); setMinimumWidth(minimumSizeHint().width() + 18); } kvpm-0.9.10/kvpm/storagepartition.cpp0000644000175000017500000001730012770324126020066 0ustar benscottbenscott /* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "storagepartition.h" #include #include #include "fsdata.h" #include "fsprobe.h" #include "misc.h" #include "physvol.h" StoragePartition::StoragePartition(PedPartition *const part, const int freespaceCount, const QList pvList, MountTables *const tables, const QStringList mdblock) : StorageBase(part, pvList, mdblock), m_ped_partition(part) { PedDevice *const ped_device = m_ped_partition->disk->dev; PedGeometry const geometry = m_ped_partition->geom; long long const sector_size = ped_device->sector_size; m_true_first_sector = geometry.start; m_first_sector = geometry.start; m_last_sector = geometry.end; m_partition_size = geometry.length * sector_size; // in bytes m_ped_type = m_ped_partition->type; const bool is_pv = isPhysicalVolume(); m_is_normal = false; m_is_extended = false; m_is_logical = false; m_is_freespace = false; m_is_logical_freespace = false; if (m_ped_type & PED_PARTITION_FREESPACE) { if (m_ped_type & PED_PARTITION_LOGICAL) { m_last_sector = getFreespaceEnd(); } m_first_sector = getAlignedStart(); m_is_freespace = true; m_partition_size = (1 + m_last_sector - m_first_sector) * sector_size; } char *const ped_path = ped_partition_get_path(part); m_name = QString(ped_path); free(ped_path); if (m_ped_type == PED_PARTITION_NORMAL) { m_partition_type = "normal"; m_is_normal = true; } else if (m_ped_type & PED_PARTITION_EXTENDED) { m_partition_type = "extended"; m_is_extended = true; } else if ((m_ped_type & PED_PARTITION_LOGICAL) && !(m_ped_type & PED_PARTITION_FREESPACE)) { m_partition_type = "logical"; m_is_logical = true; // NOTE: the device paths in PED for freespace are always numered "1" (like /dev/sda-1). // We change the "1" to an incremented number for each expanse of freespace found. } else if ((m_ped_type & PED_PARTITION_LOGICAL) && (m_ped_type & PED_PARTITION_FREESPACE)) { m_partition_type = "freespace (logical)"; m_is_logical_freespace = true; m_name.chop(1); m_name.append(QString("%1").arg(freespaceCount)); } else { m_partition_type = "freespace"; m_name.chop(1); m_name.append(QString("%1").arg(freespaceCount)); } m_name = m_name.trimmed(); m_partition_type = m_partition_type.trimmed(); // Iterate though all the possible flags and check each one PedPartitionFlag ped_flag = PED_PARTITION_BOOT; if (!m_partition_type.contains("freespace", Qt::CaseInsensitive)) { while (ped_flag != 0) { if (ped_partition_get_flag(m_ped_partition, ped_flag)) m_flags << ped_partition_flag_get_name(ped_flag); ped_flag = ped_partition_flag_next(ped_flag); } if (!m_flags.size()) m_flags << ""; } if (m_partition_type == "extended" || isMdBlock()) { m_is_mountable = false; m_fs_type = ""; } else { m_fs_type = fsprobe_getfstype2(m_name).trimmed(); m_fs_uuid = fsprobe_getfsuuid(m_name).trimmed(); m_fs_label = fsprobe_getfslabel(m_name).trimmed(); if (m_fs_type == "swap" || is_pv || m_partition_type.contains("freespace", Qt::CaseInsensitive)) m_is_mountable = false; else m_is_mountable = true; } m_mount_entries = tables->getMtabEntries(getMajorNumber(), getMinorNumber()); m_fstab_mount_point = tables->getFstabMountPoint(this); m_is_mounted = !m_mount_entries.isEmpty(); for (auto entry : m_mount_entries) m_mount_points.append(entry->getMountPoint()); if (m_is_mounted) { FSData fs_data = get_fs_data(m_mount_entries[0]->getMountPoint()); if (fs_data.size > 0) { m_fs_size = fs_data.size * fs_data.block_size; m_fs_used = fs_data.used * fs_data.block_size; } else { m_fs_size = -1; m_fs_used = -1; } } else { m_fs_size = -1; m_fs_used = -1; } if (m_partition_type == "extended") { PedPartition *temp_part = NULL; PedDisk *const temp_disk = ped_disk_new(ped_device); m_is_empty = true; if (temp_disk) { while ((temp_part = ped_disk_next_partition(temp_disk, temp_part))) { if (temp_part->type == PED_PARTITION_LOGICAL) { m_is_empty = false; break; } } ped_disk_destroy(temp_disk); } } else { m_is_empty = false; } } StoragePartition::~StoragePartition() { } int StoragePartition::getFilesystemPercentUsed() const { int percent; if (m_fs_used == 0) return 0; else if (m_fs_used == m_fs_size) return 100; else if (m_fs_size == 0) return 0; else percent = qRound(100 - (((m_fs_size - m_fs_used) * 100.0) / m_fs_size)); return percent; } /* This function returns true if the partition is extended and has no logical partitions */ bool StoragePartition::isEmptyExtended() const { return m_is_empty; } PedSector StoragePartition::getAlignedStart() { PedPartition *const free_space = m_ped_partition; PedDevice *const device = free_space->disk->dev; const PedSector ONE_MIB = 0x100000 / device->sector_size; // sectors per megabyte PedSector start = free_space->geom.start; PedSector const end = free_space->geom.length + start - 1; if ((end - start) < (ONE_MIB * 2)) // ignore partitions less than 2 MiB, alignment may fail. return start; PedAlignment *const start_align = ped_alignment_new(0, ONE_MIB); PedAlignment *const end_align = ped_alignment_new(0, 1); PedGeometry *const start_range = ped_geometry_new(device, start, free_space->geom.length); PedGeometry *const end_range = ped_geometry_new(device, start, free_space->geom.length); PedConstraint *constraint = ped_constraint_new(start_align, end_align, start_range, end_range, 1, free_space->geom.length); PedGeometry *const aligned_geometry = ped_constraint_solve_max(constraint); start = aligned_geometry->start; ped_alignment_destroy(start_align); ped_alignment_destroy(end_align); ped_constraint_destroy(constraint); ped_geometry_destroy(start_range); ped_geometry_destroy(end_range); ped_geometry_destroy(aligned_geometry); return start; } /* The following function works *only* on logical freespace. It finds the largest partition that will fit in the freespace by adding any space in a following metadata area, if one exists */ PedSector StoragePartition::getFreespaceEnd() { PedPartition *const free_space = m_ped_partition; PedDisk *const disk = free_space->disk; PedSector end = free_space->geom.length + free_space->geom.start - 1; PedPartition *next_part = ped_disk_next_partition(disk, free_space); if (next_part) { if (next_part->type & PED_PARTITION_METADATA) { end = next_part->geom.length + next_part->geom.start - 1; } } return end; } kvpm-0.9.10/kvpm/deviceactions.cpp0000644000175000017500000003771712770324126017326 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2014 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "deviceactions.h" #include "fsck.h" #include "devicesizechartseg.h" #include "mkfs.h" #include "maxfs.h" #include "mount.h" #include "unmount.h" #include "masterlist.h" #include "partremove.h" #include "partadd.h" #include "partchange.h" #include "partflag.h" #include "physvol.h" #include "storagedevice.h" #include "storagepartition.h" #include "tablecreate.h" #include "topwindow.h" #include "vgreduce.h" #include "vgcreate.h" #include "vgextend.h" #include #include #include #include DeviceActions::DeviceActions(QWidget *parent) : KActionCollection(parent) { m_act_grp = new QActionGroup(this); connect(m_act_grp, SIGNAL(triggered(QAction *)), this, SLOT(extendVg(QAction *)) ); QAction *const partchange = addAction("partchange", this, SLOT(changePartition())); partchange->setIcon(QIcon::fromTheme(QStringLiteral("document-swap"))); partchange->setText(i18n("Move or resize disk partition")); partchange->setIconText(i18n("change")); partchange->setToolTip(i18n("Move or resize disk partition")); QAction *const partflag = addAction("changeflags", this, SLOT(changeFlags())); partflag->setIcon(QIcon::fromTheme(QStringLiteral("flag-blue"))); partflag->setText(i18n("Change partition flags")); partflag->setIconText(i18n("flags")); partflag->setToolTip(i18n("Change partition flags")); QAction *const maxpv = addAction("max_pv", this, SLOT(maxFs())); maxpv->setIcon(QIcon::fromTheme(QStringLiteral("resultset_last"))); maxpv->setText(i18n("Extend physical volume to fill device")); maxpv->setIconText(i18n("Extend physical volume to fill device")); maxpv->setToolTip(i18n("Extend physical volume to fill device")); QAction *const partadd = addAction("partadd", this, SLOT(addPartition())); partadd->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); partadd->setText(i18n("Add disk partition")); partadd->setIconText(i18n("Add")); partadd->setToolTip(i18n("Add disk partition")); QAction *const partremove = addAction("partremove", this, SLOT(removePartition())); partremove->setIcon(QIcon::fromTheme(QStringLiteral("cross"))); partremove->setText(i18n("Delete disk partition")); partremove->setIconText(i18n("Delete")); partremove->setToolTip(i18n("Delete disk partition")); QAction *const vgcreate = addAction("vgcreate", this, SLOT(createVg())); vgcreate->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); vgcreate->setText(i18n("Create volume group")); vgcreate->setIconText(i18n("New vg")); vgcreate->setToolTip(i18n("Create volume group")); QAction *const tablecreate = addAction("tablecreate", this, SLOT(createTable())); tablecreate->setIcon(QIcon::fromTheme(QStringLiteral("exclamation"))); tablecreate->setText(i18n("Create or remove a partition table")); tablecreate->setIconText(i18n("Table")); tablecreate->setToolTip(i18n("Create or remove a partition table")); QAction *const vgreduce = addAction("vgreduce", this, SLOT(reduceVg())); vgreduce->setIcon(QIcon::fromTheme(QStringLiteral("delete"))); vgreduce->setText(i18n("Remove from volume group")); vgreduce->setIconText(i18n("Remove from volume group")); vgreduce->setToolTip(i18n("Remove from volume group")); QAction *const mkfs = addAction("mkfs", this, SLOT(makeFs())); mkfs->setIcon(QIcon::fromTheme(QStringLiteral("lightning_add"))); mkfs->setText(i18n("Make or remove filesystem...")); mkfs->setIconText(i18n("mkfs")); mkfs->setToolTip(i18n("Make or remove a filesystem")); QAction *const max_fs = addAction("max_fs", this, SLOT(maxFs())); max_fs->setText(i18n("Extend filesystem to fill volume...")); max_fs->setIcon(QIcon::fromTheme(QStringLiteral("resultset_last"))); max_fs->setIconText(i18n("Extend fs")); max_fs->setToolTip(i18n("Extend filesystem to fill volume")); QAction *const fsck = addAction("fsck", this, SLOT(checkFs())); fsck->setText(i18n("Run 'fsck -fp' on filesystem...")); fsck->setIcon(QIcon::fromTheme(QStringLiteral("checkmark"))); fsck->setIconText(i18n("fsck -fp")); fsck->setToolTip(i18n("Run 'fsck -fp' on the filesystem")); QAction *const mount = addAction("mount", this, SLOT(mountFs())); mount->setText(i18n("Mount filesystem...")); mount->setIconText(i18n("mount fs")); mount->setToolTip(i18n("Mount filesystem")); mount->setIcon(QIcon::fromTheme(QStringLiteral("emblem-mounted"))); QAction *const unmount = addAction("unmount", this, SLOT(unmountFs())); unmount->setText(i18n("Unmount filesystem...")); unmount->setIconText(i18n("Unmount fs")); unmount->setToolTip(i18n("Unmount filesystem")); unmount->setIcon(QIcon::fromTheme(QStringLiteral("emblem-unmounted"))); changeDevice(nullptr); } void DeviceActions::changeDevice(QTreeWidgetItem *item) { QAction *const mkfs = action("mkfs"); QAction *const fsck = action("fsck"); QAction *const max_fs = action("max_fs"); QAction *const max_pv = action("max_pv"); QAction *const partflag = action("changeflags"); QAction *const partremove = action("partremove"); QAction *const partadd = action("partadd"); QAction *const partchange = action("partchange"); QAction *const vgcreate = action("vgcreate"); QAction *const tablecreate = action("tablecreate"); QAction *const vgreduce = action("vgreduce"); QAction *const mount = action("mount"); QAction *const unmount = action("unmount"); QList extend_actions = m_act_grp->actions(); for (auto action : extend_actions) { m_act_grp->removeAction(action); removeAction(action); // also deletes the action } for (auto vg : MasterList::getVgNames()) { QAction *const action = addAction(vg, this); m_act_grp->addAction(action); action->setText(vg); } if (item) { m_dev = static_cast((item->data(1, Qt::UserRole)).value()); if(m_dev->isDmBlock() || m_dev->isMdBlock()) { mkfs->setEnabled(false); fsck->setEnabled(false); max_fs->setEnabled(false); max_pv->setEnabled(false); partflag->setEnabled(false); partremove->setEnabled(false); partadd->setEnabled(false); partchange->setEnabled(false); vgcreate->setEnabled(false); tablecreate->setEnabled(false); vgreduce->setEnabled(false); mount->setEnabled(false); unmount->setEnabled(false); vgextendEnable(false); } else if ((item->data(0, Qt::UserRole)).canConvert()) { // its a partition m_part = static_cast((item->data(0, Qt::UserRole)).value()); tablecreate->setEnabled(false); mount->setEnabled(m_part->isMountable()); unmount->setEnabled(m_part->isMounted()); fsck->setEnabled(!m_part->isMounted() && !m_part->isBusy()); if (m_part->isMdBlock()) { mkfs->setEnabled(false); fsck->setEnabled(false); max_fs->setEnabled(false); max_pv->setEnabled(false); partflag->setEnabled(false); partremove->setEnabled(false); partadd->setEnabled(false); partchange->setEnabled(false); vgcreate->setEnabled(false); tablecreate->setEnabled(false); vgreduce->setEnabled(false); mount->setEnabled(false); unmount->setEnabled(false); vgextendEnable(false); } else if (m_part->getPedType() & 0x04) { // freespace max_fs->setEnabled(false); max_pv->setEnabled(false); mkfs->setEnabled(false); partremove->setEnabled(false); partchange->setEnabled(false); partadd->setEnabled(true); vgcreate->setEnabled(false); vgextendEnable(false); vgreduce->setEnabled(false); partflag->setEnabled(false); fsck->setEnabled(false); } else if (m_part->getPedType() & 0x02) { // extended partition max_fs->setEnabled(false); max_pv->setEnabled(false); mkfs->setEnabled(false); if (m_part->isEmptyExtended()) { partadd->setEnabled(true); partremove->setEnabled(true); } else { partadd->setEnabled(false); partremove->setEnabled(false); } partchange->setEnabled(false); vgcreate->setEnabled(false); vgextendEnable(false); vgreduce->setEnabled(false); partflag->setEnabled(false); fsck->setEnabled(false); } else if (m_part->isPhysicalVolume()) { max_fs->setEnabled(false); mkfs->setEnabled(false); fsck->setEnabled(false); partremove->setEnabled(false); if (m_part->getPhysicalVolume()->isActive()) partchange->setEnabled(false); else partchange->setEnabled(true); max_pv->setEnabled(true); partadd->setEnabled(false); vgcreate->setEnabled(false); vgextendEnable(false); if (m_part->getPhysicalVolume()->getPercentUsed() == 0) vgreduce->setEnabled(true); else vgreduce->setEnabled(false); partflag->setEnabled(true); } else if (m_part->isNormal() || m_part->isLogical()) { max_pv->setEnabled(false); if (m_part->isMounted() || m_part->isBusy()) { partremove->setEnabled(false); partchange->setEnabled(false); mkfs->setEnabled(false); max_fs->setEnabled(false); vgcreate->setEnabled(false); vgextendEnable(false); } else { // not mounted or busy partremove->setEnabled(true); partchange->setEnabled(true); max_fs->setEnabled(true); mkfs->setEnabled(true); vgcreate->setEnabled(true); } partadd->setEnabled(false); vgreduce->setEnabled(false); partflag->setEnabled(true); } } else { // its a whole device m_part = nullptr; mount->setEnabled(false); unmount->setEnabled(false); max_fs->setEnabled(false); fsck->setEnabled(false); partflag->setEnabled(false); if (m_dev->isPhysicalVolume()) { tablecreate->setEnabled(false); mkfs->setEnabled(false); max_pv->setEnabled(true); partremove->setEnabled(false); partchange->setEnabled(false); partadd->setEnabled(false); vgcreate->setEnabled(false); vgextendEnable(false); if (m_dev->getPhysicalVolume()->getPercentUsed() == 0) vgreduce->setEnabled(true); else vgreduce->setEnabled(false); } else { // not a pv partremove->setEnabled(false); partchange->setEnabled(false); partadd->setEnabled(false); mkfs->setEnabled(false); vgreduce->setEnabled(false); max_pv->setEnabled(false); if (m_dev->isBusy() || m_dev->getRealPartitionCount() != 0) { tablecreate->setEnabled(false); vgcreate->setEnabled(false); vgextendEnable(false); } else { tablecreate->setEnabled(true); vgcreate->setEnabled(true); vgextendEnable(true); } } } } else { mkfs->setEnabled(false); fsck->setEnabled(false); max_fs->setEnabled(false); max_pv->setEnabled(false); partflag->setEnabled(false); partremove->setEnabled(false); partadd->setEnabled(false); partchange->setEnabled(false); vgcreate->setEnabled(false); tablecreate->setEnabled(false); vgreduce->setEnabled(false); mount->setEnabled(false); unmount->setEnabled(false); vgextendEnable(false); } } void DeviceActions::vgextendEnable(bool enable) { for (auto action : m_act_grp->actions()) action->setEnabled(enable); } void DeviceActions::makeFs() { MkfsDialog dialog(m_part); if (dialog.run() == QDialog::Accepted) g_top_window->reRun(); } void DeviceActions::checkFs() { if (manual_fsck(m_part)) g_top_window->reRun(); } void DeviceActions::maxFs() { if (m_part) { // m_part == nullptr if not a partition if (max_fs(m_part)) g_top_window->reRun(); } else { if (max_fs(m_dev)) g_top_window->reRun(); } } void DeviceActions::removePartition() { if (remove_partition(m_part)) g_top_window->reRun(); } void DeviceActions::addPartition() { PartitionAddDialog dialog(m_part); if (!dialog.bailout()) { dialog.exec(); if (dialog.result() == QDialog::Accepted) g_top_window->reRun(); } } void DeviceActions::changePartition() { PartitionChangeDialog dialog(m_part); if (!dialog.bailout()) { dialog.exec(); if (dialog.result() == QDialog::Accepted) g_top_window->reRun(); } } void DeviceActions::changeFlags() { PartitionFlagDialog dialog(m_part); if (!dialog.bailout()) { dialog.exec(); if (dialog.result() == QDialog::Accepted) g_top_window->reRun(); } } void DeviceActions::createVg() { VGCreateDialog *dialog; if (m_part) dialog = new VGCreateDialog(m_part); else dialog = new VGCreateDialog(m_dev); if (dialog->run() == QDialog::Accepted) g_top_window->reRun(); dialog->deleteLater(); } void DeviceActions::createTable() { if (create_table(m_dev->getName())) g_top_window->reRun(); } void DeviceActions::reduceVg() // pvs can also be whole devices { PhysVol *pv = nullptr; if (m_part) pv = m_part->getPhysicalVolume(); else pv = m_dev->getPhysicalVolume(); VGReduceDialog dialog(pv); if (dialog.run() == KDialog::Yes) g_top_window->reRun(); } void DeviceActions::mountFs() { MountDialog dialog(m_part); if (dialog.run() == QDialog::Accepted) g_top_window->reRun(); } void DeviceActions::unmountFs() { UnmountDialog dialog(m_part); int result = dialog.run(); if (result == QDialog::Accepted || result == KDialog::Yes) g_top_window->reRun(); } void DeviceActions::extendVg(QAction *action) { const QString vg_name = action->objectName(); VolGroup *const vg = MasterList::getVgByName(vg_name); KvpmDialog *dialog; if (m_part) dialog = new VGExtendDialog(vg, m_part); else dialog = new VGExtendDialog(vg, m_dev); // whole device, not partition if (dialog->run() == QDialog::Accepted) g_top_window->reRun(); dialog->deleteLater(); } kvpm-0.9.10/kvpm/mounttables.cpp0000644000175000017500000002327312770324126017033 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2013, 2014 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "mounttables.h" #include #include #include #include #include #include #include #include #include #include "logvol.h" #include "mountentry.h" #include "storagepartition.h" namespace // The following functions are only needed locally { const int BUFF_LEN = 2000; // Enough? bool isWritableFile(QString mtab) { QFileInfo fi = QFileInfo(mtab); if (fi.isSymLink()) return false; else return fi.isWritable(); } /* This function generates an mntent structure from its parameters and returns it */ mntent* buildMntent(const QString device, const QString mountPoint, const QString type, const QString options, const int dumpFreq, const int pass) { QByteArray device_array = device.toLocal8Bit(); QByteArray mount_point_array = mountPoint.toLocal8Bit(); QByteArray type_array = type.toLocal8Bit(); QByteArray options_array = options.toLocal8Bit(); mntent *mount_entry = new mntent; mount_entry->mnt_fsname = device_array.data(); mount_entry->mnt_dir = mount_point_array.data(); mount_entry->mnt_type = type_array.data(); mount_entry->mnt_opts = options_array.data(); mount_entry->mnt_freq = dumpFreq; mount_entry->mnt_passno = pass; return mount_entry; } mntent *copyMntent(mntent *const entry) { if (entry == NULL) return NULL; mntent *new_entry = new mntent; new_entry->mnt_fsname = new char[BUFF_LEN]; new_entry->mnt_dir = new char[BUFF_LEN]; new_entry->mnt_type = new char[BUFF_LEN]; new_entry->mnt_opts = new char[BUFF_LEN]; strncpy(new_entry->mnt_fsname, entry->mnt_fsname, BUFF_LEN); strncpy(new_entry->mnt_dir, entry->mnt_dir, BUFF_LEN); strncpy(new_entry->mnt_type, entry->mnt_type, BUFF_LEN); strncpy(new_entry->mnt_opts, entry->mnt_opts, BUFF_LEN); new_entry->mnt_freq = entry->mnt_freq; new_entry->mnt_passno = entry->mnt_passno; return new_entry; } void deleteMntent(mntent *const entry) { if (entry != NULL) { delete entry->mnt_fsname; delete entry->mnt_dir; delete entry->mnt_type; delete entry->mnt_opts; delete entry; } } } MountTables::MountTables() { } MountTables::~MountTables() { } void MountTables::loadData() { m_fstab_list.clear(); // smart pointers delete data m_mount_list.clear(); // when the lists are cleared QString line; QFile file("/proc/self/mountinfo"); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&file); while (!(line = in.readLine()).isEmpty()) { QStringList subs = line.section(' ', 2, 2).split(':'); // major and minor dev numbers m_mount_list.append(MountPtr(new MountEntry(line, subs[0].toInt(), subs[1].toInt()))); } } file.close(); for (int x = m_mount_list.size() - 1; x >= 0; --x) { int pos = 1; if (m_mount_list[x]->getMountPosition() > 0) continue; for (int y = x - 1; y >= 0; y--) { if (m_mount_list[y]->getMountPoint() == m_mount_list[x]->getMountPoint()) m_mount_list[y]->setMountPosition(++pos); } if (pos > 1) m_mount_list[x]->setMountPosition(1); } mntent *entry; FILE *fp; if ((fp = setmntent(_PATH_FSTAB, "r"))) { while ((entry = getmntent(fp))) m_fstab_list.append(MountPtr(new MountEntry(entry))); endmntent(fp); } } // Major and minor numbers don't change when lv or vg names are changed // Also lookups by fs uuid won't work with snaps since they will match the origin // and each other. MountList MountTables::getMtabEntries(const int major, const int minor) { MountList device_mounts; for (int x = 0; x < m_mount_list.size(); ++x) { if (m_mount_list[x]->getMajorNumber() == major && m_mount_list[x]->getMinorNumber() == minor) { device_mounts.append(MountPtr(new MountEntry(m_mount_list[x].data()))); } } return device_mounts; } QString MountTables::getFstabMountPoint(LogVol *const lv) { return getFstabMountPoint(lv->getMapperPath(), lv->getFilesystemLabel(), lv->getFilesystemUuid()); } QString MountTables::getFstabMountPoint(StoragePartition *const partition) { return getFstabMountPoint(partition->getName(), partition->getFilesystemLabel(), partition->getFilesystemUuid()); } QString MountTables::getFstabMountPoint(const QString name, const QString label, const QString uuid) { QString entry_name; MountPtr entry; QMutableListIterator entry_itr(m_fstab_list); while (entry_itr.hasNext()) { entry = entry_itr.next(); entry_name = entry->getDeviceName(); if (entry_name.startsWith("UUID=", Qt::CaseInsensitive)) { entry_name = entry_name.remove(0, 5); if (entry_name == uuid) return entry->getMountPoint(); } else if (entry_name.startsWith("LABEL=", Qt::CaseInsensitive)) { entry_name = entry_name.remove(0, 6); if (entry_name == label) return entry->getMountPoint(); } else if (entry_name == name) return entry->getMountPoint(); } return QString(); } // Adds an entry (a mntent struct) into the mount table file, usually /etc/mtab. bool MountTables::addEntry(const QString device, const QString mountPoint, const QString type, const QString options, const int dumpFreq, const int pass) { if (!isWritableFile(_PATH_MOUNTED)) return true; QByteArray device_array = device.toLocal8Bit(); QByteArray mount_point_array = mountPoint.toLocal8Bit(); QByteArray type_array = type.toLocal8Bit(); QByteArray options_array = options.toLocal8Bit(); const struct mntent mount_entry = { device_array.data(), mount_point_array.data(), type_array.data(), options_array.data(), dumpFreq, pass }; FILE *const fp = setmntent(_PATH_MOUNTED, "a"); if (fp) { if (addmntent(fp, &mount_entry)) { fchmod(fileno(fp), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); fsync(fileno(fp)); endmntent(fp); return false; } else { // success fchmod(fileno(fp), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); fsync(fileno(fp)); endmntent(fp); return true; } } else return false; } bool MountTables::removeEntry(const QString mountPoint) { if (!isWritableFile(_PATH_MOUNTED)) return true; QList mount_entry_list; mntent *mount_entry; QByteArray new_path(_PATH_MOUNTED); new_path.append(".new"); const char *mount_table_old = _PATH_MOUNTED; const char *mount_table_new = new_path.data(); FILE *fp_old = setmntent(mount_table_old, "r"); /* Multiple devices can be mounted on a directory but only the last one mounted will be unmounted at one time. So only the last mtab entry gets deleted upon unmounting */ while ((mount_entry = copyMntent(getmntent(fp_old)))) mount_entry_list.append(mount_entry); for (int x = mount_entry_list.size() - 1; x >= 0; x--) { if (QString((mount_entry_list[x])->mnt_dir) == mountPoint) { deleteMntent(mount_entry_list.takeAt(x)); break; } } endmntent(fp_old); FILE *fp_new = setmntent(mount_table_new, "w"); for (int x = 0; x < mount_entry_list.size(); x++) { const mntent *entry = mount_entry_list[x]; addmntent(fp_new, entry); } fchmod(fileno(fp_new), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); fsync(fileno(fp_new)); endmntent(fp_new); KDE_rename(mount_table_new, mount_table_old); return true; } bool MountTables::renameEntries(const QString oldName, const QString newName) { if (!isWritableFile(_PATH_MOUNTED)) return true; QList mount_entry_list; mntent *mount_entry; mntent *temp_entry; QByteArray new_path(_PATH_MOUNTED); new_path.append(".new"); const char *mount_table_old = _PATH_MOUNTED; const char *mount_table_new = new_path.data(); FILE *fp_old = setmntent(mount_table_old, "r"); while ((mount_entry = copyMntent(getmntent(fp_old)))) { if (QString(mount_entry->mnt_fsname) != oldName) { mount_entry_list.append(mount_entry); } else { temp_entry = buildMntent(newName, mount_entry->mnt_dir, mount_entry->mnt_type, mount_entry->mnt_opts, mount_entry->mnt_freq, mount_entry->mnt_passno); deleteMntent(mount_entry); mount_entry_list.append(temp_entry); } } endmntent(fp_old); FILE *fp_new = setmntent(mount_table_new, "w"); for (int x = 0; x < mount_entry_list.size(); x++) { const mntent *entry = mount_entry_list[x]; addmntent(fp_new, entry); } fchmod(fileno(fp_new), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); fsync(fileno(fp_new)); endmntent(fp_new); KDE_rename(mount_table_new, mount_table_old); return true; } kvpm-0.9.10/kvpm/pvactionsmenu.h0000644000175000017500000000113012770324126017022 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVACTIONSMENU_H #define PVACTIONSMENU_H #include class PVActions; class PVActionsMenu : public QMenu { Q_OBJECT public: PVActionsMenu(PVActions *const pvactions, QWidget *parent); }; #endif kvpm-0.9.10/kvpm/pvactions.h0000644000175000017500000000157612770324126016153 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVACTIONS_H #define PVACTIONS_H #include class QTreeWidgetItem; class QAction; class PhysVol; class VolGroup; class PVActions : public KActionCollection { Q_OBJECT VolGroup *m_vg = nullptr; PhysVol *m_pv = nullptr; void setActions(PhysVol *const pv, bool const isMoving); public: PVActions(VolGroup *const group, QWidget *parent = nullptr); public slots: void changePv(QTreeWidgetItem *item); private slots: void callDialog(QAction *); }; #endif kvpm-0.9.10/kvpm/storagedevice.cpp0000644000175000017500000000471412770324126017321 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include #include "storagedevice.h" #include "storagepartition.h" #include "physvol.h" #include "mounttables.h" StorageDevice::StorageDevice(PedDevice *const pedDevice, const QList pvList, MountTables *const tables, const QStringList dmblock, const QStringList dmraid, const QStringList mdblock, const QStringList mdraid) : StorageBase(pedDevice, pvList, dmblock, dmraid, mdblock, mdraid) { const long long sector_size = getSectorSize(); const bool is_pv = isPhysicalVolume(); m_freespace_count = 0; m_physical_sector_size = pedDevice->phys_sector_size; m_hardware = QString(pedDevice->model); m_device_size = pedDevice->length * sector_size; PedDisk *const disk = ped_disk_new(pedDevice); if (disk && !is_pv && !isDmBlock()) { PedDiskFlag cylinder_flag = ped_disk_flag_get_by_name("cylinder_alignment"); if (ped_disk_is_flag_available(disk, cylinder_flag)) { ped_disk_set_flag(disk, cylinder_flag, 0); } m_disk_label = QString(disk->type->name); PedPartition *part = nullptr; while ((part = ped_disk_next_partition(disk, part))) { const long long length = part->geom.length * sector_size; const PedPartitionType pt = part->type; // ignore freespace less than 3 megs if (!((pt & PED_PARTITION_METADATA) || ((pt & PED_PARTITION_FREESPACE) && (length < (0x300000))))) { if (pt & PED_PARTITION_FREESPACE) m_freespace_count++; m_storage_partitions.append(new StoragePartition(part, m_freespace_count, pvList, tables, mdblock)); } } } else if (is_pv) { m_disk_label = "physical volume"; } else { m_disk_label = "unknown"; } } StorageDevice::~StorageDevice() { for (auto *part : m_storage_partitions) delete part; } kvpm-0.9.10/kvpm/misc.cpp0000644000175000017500000001106212770324126015422 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "misc.h" #include #include #include #include #include #include #include //#include #include "masterlist.h" #include "storagebase.h" #include "storagedevice.h" #include "storagepartition.h" NoMungeCheck::NoMungeCheck(const QString text, QWidget *parent) : QCheckBox(text, parent) { m_unmunged_text = text; } QString NoMungeCheck::getAlternateText() { return m_alternate_text; } QStringList NoMungeCheck::getAlternateTextList() { return m_alternate_text_list; } QString NoMungeCheck::getUnmungedText() { return m_unmunged_text; } void NoMungeCheck::setAlternateText(QString alternateText) { m_alternate_text = alternateText; } void NoMungeCheck::setAlternateTextList(QStringList alternateTextList) { m_alternate_text_list = alternateTextList; } void NoMungeCheck::setData(QVariant data) { m_data = data; } QVariant NoMungeCheck::getData() { return m_data; } NoMungeRadioButton::NoMungeRadioButton(const QString text, QWidget *parent) : QRadioButton(text, parent) { m_unmunged_text = text; } QString NoMungeRadioButton::getAlternateText() { return m_alternate_text; } QString NoMungeRadioButton::getUnmungedText() { return m_unmunged_text; } void NoMungeRadioButton::setAlternateText(QString alternateText) { m_alternate_text = alternateText; } void NoMungeRadioButton::setData(QVariant data) { m_data = data; } QVariant NoMungeRadioButton::getData() { return m_data; } QStringList splitUuid(QString const uuid) // Turns a one line uuid into two shorter lines for veiwing { int split_index = 0; if (uuid.count('-') < 4) { return QStringList(uuid) << ""; } else if (uuid.count('-') < 5) { for (int x = 0; x < 4; x++) split_index = uuid.indexOf('-', split_index + 1); } else { for (int x = 0; x < 5; x++) split_index = uuid.indexOf('-', split_index + 1); } if (split_index <= 0) split_index = uuid.size() - 1; QString const uuid_start = uuid.left(split_index + 1); QString const uuid_end = uuid.right((uuid.size() - split_index) - 1); return QStringList(uuid_start) << uuid_end; } // Checks to see if a device is busy on linux 2.6+ bool isBusy(const QString device) { struct stat st_buf; int fd; QByteArray dev_qba = device.toLocal8Bit(); if (stat(dev_qba.data(), &st_buf) != 0) return false; fd = open(dev_qba.data(), O_RDONLY | O_EXCL); if (fd < 0) { if (errno == EBUSY) return true; } else close(fd); return false; } QString findMapperPath(QString name) { QString mapper_name; QFileInfo fi(name); if (fi.exists()) mapper_name = fi.canonicalFilePath(); else return name; QByteArray qba = mapper_name.toLocal8Bit(); struct stat fs; if (stat(qba.data(), &fs)) // error return name; dm_lib_init(); dm_log_with_errno_init(NULL); char buf[1000]; if (!dm_device_get_name(major(fs.st_rdev), minor(fs.st_rdev), 0, buf, 1000)) return name; dm_lib_release(); mapper_name = QString("/dev/mapper/").append(QString(buf)); fi.setFile(mapper_name); if(fi.exists()) return mapper_name; else return name; } // returns a list of pvs suitable for creating or extending a vg QList getUsablePvs() { QList devices; for (auto dev : MasterList::getStorageDevices() ) { if (!dev->isDmBlock() && !dev->isMdBlock() && !dev->isPhysicalVolume()) { if ((dev->getRealPartitionCount() == 0) && !dev->isBusy()) { devices.append(dev); } else if (dev->getRealPartitionCount() > 0) { for (auto part : dev->getStoragePartitions()) { if (!part->isBusy() && !part->isPhysicalVolume() && ((part->isNormal()) || (part->isLogical()))) { if (!part->isDmBlock() && !part->isMdBlock()) devices.append(part); } } } } } return devices; } kvpm-0.9.10/kvpm/partadd.cpp0000644000175000017500000001341412770324126016111 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "partadd.h" #include "pedexceptions.h" #include "storagepartition.h" #include #include #include #include #include #include // The "partition" we get here is usually a ped pointer to a the freespace // between partitions. It can also be an *empty* extended partition however. PartitionAddDialog::PartitionAddDialog(StoragePartition *const partition, QWidget *parent) : PartitionDialogBase(partition, parent), m_storage_partition(partition) { if (!(m_bailout = hasInitialErrors())) { insertWidget(buildTypeWidget()); validateChange(); connect(this, SIGNAL(changed()), this, SLOT(validateChange())); connect(this, SIGNAL(okClicked()), this, SLOT(commitPartition())); } } void PartitionAddDialog::validateChange() { const PedSector sector_size = getSectorSize(); const PedSector ONE_MIB = 0x100000 / sector_size; // sectors per megabyte const long long offset = getNewOffset(); const long long size = getNewSize(); updateGraphicAndLabels(); if ((offset <= getMaxSize() - size) && (size <= getMaxSize() - offset) && (size >= 2 * ONE_MIB) && (isValid())) { enableButtonOk(true); } else { enableButtonOk(false); } } QGroupBox* PartitionAddDialog::buildTypeWidget() { QGroupBox *const group = new QGroupBox(this); QVBoxLayout *const layout = new QVBoxLayout(); layout->addSpacing(10); QLabel *const type_label = new QLabel(i18n("Select type: ")); QHBoxLayout *const type_layout = new QHBoxLayout; m_type_combo = new QComboBox; PedPartition *const free_space = getPedPartition(); bool in_extended; // true if we are inside an extended partition bool extended_ok; // true if we can create an extended partition here // check to see if partition table supports extended // partitions and if it already has one PedDisk *const disk = getPedPartition()->disk; if ((PED_DISK_TYPE_EXTENDED & disk->type->features) && (!ped_disk_extended_partition(disk))) extended_ok = true; else extended_ok = false; if (free_space->type & PED_PARTITION_LOGICAL) in_extended = true; else if (free_space->type & PED_PARTITION_EXTENDED) in_extended = true; else in_extended = false; m_type_combo->insertItem(0, i18n("Primary")); m_type_combo->insertItem(1, i18n("Extended")); if (in_extended) { m_type_combo->insertItem(2, i18n("Logical")); m_type_combo->setEnabled(false); m_type_combo->setCurrentIndex(2); } else if (!extended_ok) { m_type_combo->setEnabled(false); m_type_combo->setCurrentIndex(0); } type_label->setBuddy(m_type_combo); type_layout->addWidget(type_label); type_layout->addWidget(m_type_combo); type_layout->addStretch(); layout->addLayout(type_layout); layout->addSpacing(10); group->setLayout(layout); return group; } bool PartitionAddDialog::bailout() { return m_bailout; } void PartitionAddDialog::commitPartition() { const PedSector ONE_MIB = 0x100000 / getSectorSize(); // sectors per megabyte PedDevice *const device = getPedPartition()->disk->dev; PedDisk *const disk = getPedPartition()->disk; const PedSector max_start = getMaxStart(); const PedSector max_end = getMaxEnd(); PedSector first_sector = max_start + getNewOffset(); PedSector last_sector = first_sector + getNewSize() - 1; PedPartitionType type; if (first_sector < max_start) first_sector = max_start; if (last_sector > max_end) last_sector = max_end; if ((1 + last_sector - first_sector) < (2 * ONE_MIB)) { KMessageBox::sorry(0, i18n("Partitions less than two MiB are not supported")); return; } PedAlignment *const start_align = ped_alignment_new(0, ONE_MIB); PedAlignment *const end_align = ped_alignment_new(-1, ONE_MIB); PedGeometry *const max_geom = ped_geometry_new(device, max_start, 1 + max_end - max_start); first_sector = ped_alignment_align_nearest(start_align, max_geom, first_sector); last_sector = ped_alignment_align_nearest(end_align, max_geom, last_sector); ped_geometry_destroy(max_geom); const PedSector length = 1 + last_sector - first_sector; PedGeometry *const start_range = ped_geometry_new(device, first_sector, length); PedGeometry *const end_range = ped_geometry_new(device, first_sector, length); PedConstraint *const constraint = ped_constraint_new(start_align, end_align, start_range, end_range, length - ONE_MIB, length); if (constraint != NULL) { if (m_type_combo->currentIndex() == 0) type = PED_PARTITION_NORMAL ; else if (m_type_combo->currentIndex() == 1) type = PED_PARTITION_EXTENDED ; else type = PED_PARTITION_LOGICAL ; PedPartition *ped_new_partition = ped_partition_new(disk, type, 0, first_sector, last_sector); if (ped_new_partition != NULL) { if (ped_disk_add_partition(disk, ped_new_partition, constraint)) ped_disk_commit(disk); } ped_constraint_destroy(constraint); } ped_geometry_destroy(start_range); ped_geometry_destroy(end_range); } kvpm-0.9.10/kvpm/removemirror.cpp0000644000175000017500000000612312770324126017221 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "removemirror.h" #include #include #include #include "logvol.h" #include "misc.h" #include "processprogress.h" #include "volgroup.h" RemoveMirrorDialog::RemoveMirrorDialog(LogVol *mirror, QWidget *parent) : KvpmDialog(parent) { while (mirror->isLvmMirrorLog() || mirror->isLvmMirrorLeg()) mirror = mirror->getParentMirror(); m_lv = mirror; m_vg = m_lv->getVg(); setCaption(i18n("Remove Mirrors")); QWidget *const dialog_body = new QWidget(this); QVBoxLayout *const layout = new QVBoxLayout; dialog_body->setLayout(layout); setMainWidget(dialog_body); QLabel *const message = new QLabel(i18n("Select mirror legs to remove from %1", m_lv->getName())); layout->addSpacing(5); layout->addWidget(message); layout->addSpacing(10); for (auto lv : m_lv->getChildren()) { if (lv->isMirrorLeg()) { NoMungeCheck *const check = new NoMungeCheck(lv->getName()); check->setAlternateTextList(lv->getPvNamesAll()); m_leg_checks.append(check); layout->addWidget(check); connect(check, SIGNAL(stateChanged(int)), this, SLOT(validateCheckStates())); } } layout->addSpacing(10); validateCheckStates(); } void RemoveMirrorDialog::commit() { hide(); int mirror_count = m_leg_checks.size(); QStringList legs; // mirror legs (actually pv names) being deleted for (auto check : m_leg_checks) { if (check->isChecked()) { legs << check->getAlternateTextList(); mirror_count--; } } QStringList args = QStringList() << "lvconvert" << "--mirrors" << QString("%1").arg(mirror_count - 1) << m_lv->getFullName() << legs; ProcessProgress remove(args); } /* One leg of the mirror must always be left intact, so we make certain at least one check box is left unchecked. The unchecked one is disabled. */ void RemoveMirrorDialog::validateCheckStates() { const int check_box_count = m_leg_checks.size(); int checked_count = 0; for (auto checkbox : m_leg_checks) { if (checkbox->isChecked()) { ++checked_count; } } enableButtonOk(static_cast(checked_count)); if (checked_count == (check_box_count - 1)) { for (auto checkbox : m_leg_checks) { if (!checkbox->isChecked()) checkbox->setEnabled(false); } } else { for (auto checkbox : m_leg_checks) checkbox->setEnabled(true); } } kvpm-0.9.10/kvpm/fsck.h0000644000175000017500000000114312770324126015061 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef FSCK_H #define FSCK_H class QString; class LogVol; class StoragePartition; bool fsck(const QString path); bool manual_fsck(LogVol *const logicalVolume); bool manual_fsck(StoragePartition *const partition); #endif kvpm-0.9.10/kvpm/volumegrouptab.h0000644000175000017500000000330212770324126017205 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VOLUMEGROUPTAB_H #define VOLUMEGROUPTAB_H #include #include class QVBoxLayout; class QTreeWidgetItem; \ class KToolBar; class VolGroup; class LVPropertiesStack; class PVPropertiesStack; class PVTree; class VGTree; class VGInfoLabels; class VGWarning; class LVSizeChart; class LVActions; class PVActions; class LogVol; class VolumeGroupTab : public KMainWindow { Q_OBJECT VGWarning *m_vg_warning = nullptr; LVActions *m_lv_actions = nullptr; PVActions *m_pv_actions = nullptr; LVSizeChart *m_lv_size_chart = nullptr; VGInfoLabels *m_vg_info_labels = nullptr; QVBoxLayout *m_layout = nullptr; VolGroup *m_vg = nullptr; LVPropertiesStack *m_lv_properties_stack = nullptr; PVPropertiesStack *m_pv_properties_stack = nullptr; PVTree *m_pv_tree = nullptr; VGTree *m_vg_tree = nullptr; void readConfig(); KToolBar *buildLvToolBar(); KToolBar *buildPvToolBar(); public: explicit VolumeGroupTab(VolGroup *const group, QWidget *parent = nullptr); VolGroup *getVg(); QMenu *createPopupMenu() { return nullptr; } void rescan(); public slots: void lvContextMenu(QTreeWidgetItem *item); void lvContextMenu(LogVol *lv); void pvContextMenu(QTreeWidgetItem *item); }; #endif kvpm-0.9.10/kvpm/nohup.out0000600000175000017500000000000012770324126015623 0ustar benscottbenscottkvpm-0.9.10/kvpm/vgreduce.h0000644000175000017500000000234612770324126015745 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGREDUCE_H #define VGREDUCE_H #include "kvpmdialog.h" #include #include class QStackedWidget; class PhysVol; class VolGroup; class PvGroupBox; class PvSpace; class VGReduceDialog : public KvpmDialog { Q_OBJECT PhysVol *m_pv = nullptr; const VolGroup *m_vg = nullptr; PvGroupBox *m_pv_checkbox = nullptr; QStackedWidget *m_error_stack = nullptr; QList > getPvSpaceList(); bool hasUnremovablePv(); bool hasMda(const QStringList remove); QStackedWidget *createErrorWidget(); public: explicit VGReduceDialog(VolGroup *const group, QWidget *parent = nullptr); explicit VGReduceDialog(PhysVol *const pv, QWidget *parent = nullptr); private slots: void resetOkButton(); // one pv must remain in the vg void commit(); }; #endif kvpm-0.9.10/kvpm/pvextend.h0000644000175000017500000000074512770324126015777 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVEXTEND_H #define PVEXTEND_H class QString; bool pv_extend(QString path); #endif kvpm-0.9.10/kvpm/fsprobe.h0000644000175000017500000000113712770324126015576 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef FSPROBE_H #define FSPROBE_H class QString; QString fsprobe_getfstype2(const QString devicePath); QString fsprobe_getfsuuid(const QString devicePath); QString fsprobe_getfslabel(const QString devicePath); #endif kvpm-0.9.10/kvpm/pedthread.h0000644000175000017500000000255612770324126016104 0ustar benscottbenscott/* * * * Copyright (C) 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PEDTHREAD_H #define PEDTHREAD_H #include #include #include #include class PedThread : public QThread { private: QSemaphore *m_semaphore; public: PedThread(QSemaphore *semaphore) : m_semaphore(semaphore) { m_semaphore->acquire(); } protected: void run() { // the ped_device_free_all() sometimes causes ped to freeze up completely // during ped_device_probe_all() if a device is removed, even if the device come back. // ped_device_free_all(); PedDevice *dev = NULL; QList dev_list; // removing them one by one seems to work ok. while ((dev = ped_device_get_next(dev))) { dev_list << dev; } for (auto device : dev_list) { ped_device_cache_remove(device); ped_device_destroy(device); } ped_device_probe_all(); m_semaphore->release(); } }; #endif kvpm-0.9.10/kvpm/repairmissing.h0000644000175000017500000000232412770324126017011 0ustar benscottbenscott/* * * * Copyright (C) 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef REPAIRMISSING_H #define REPAIRMISSING_H #include class QRadioButton; class QWidget; #include "logvol.h" #include "kvpmdialog.h" class PhysVol; class PvGroupBox; class RepairMissingDialog : public KvpmDialog { Q_OBJECT PvGroupBox *m_pv_box = nullptr; QRadioButton *m_replace_radio = nullptr; LogVol *m_lv = nullptr; // The volume we are repairing QStringList arguments(); QList getUsablePvs(); QList getSelectedPvs(); LvList getPartialLvs(); QWidget *buildPhysicalWidget(QList const pvs); int getImageNumber(QString name); public: explicit RepairMissingDialog(LogVol *const volume, QWidget *parent = nullptr); private slots: void resetOkButton(); void commit(); void setReplace(const bool replace); }; #endif kvpm-0.9.10/kvpm/devicetab.cpp0000644000175000017500000001305112770324126016415 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "devicetab.h" #include "devicetree.h" #include "devicesizechart.h" #include "deviceproperties.h" #include "devicepropertiesstack.h" #include "deviceactions.h" #include "devicemenu.h" #include "storagedevice.h" #include #include #include #include #include DeviceTab::DeviceTab(QWidget *parent) : KMainWindow(parent) { QWidget *const central = new QWidget(); setCentralWidget(central); m_layout = new QVBoxLayout; central->setLayout(m_layout); m_size_chart = new DeviceSizeChart(this); m_device_stack = new DevicePropertiesStack(this); m_tree = new DeviceTree(m_size_chart, m_device_stack, this); m_device_actions = new DeviceActions(this); addToolBar(buildDeviceToolBar()); m_tree_properties_splitter = new QSplitter(Qt::Horizontal); m_layout->addWidget(m_size_chart); m_layout->addWidget(m_tree_properties_splitter); m_tree_properties_splitter->addWidget(m_tree); m_tree_properties_splitter->addWidget(setupPropertyStack()); m_tree_properties_splitter->setStretchFactor(0, 9); m_tree_properties_splitter->setStretchFactor(1, 2); connect(m_tree, SIGNAL(deviceMenuRequested(QTreeWidgetItem*)), this, SLOT(deviceContextMenu(QTreeWidgetItem*))); connect(m_size_chart, SIGNAL(deviceMenuRequested(QTreeWidgetItem*)), this, SLOT(deviceContextMenu(QTreeWidgetItem*))); } void DeviceTab::rescan(QList devices) { disconnect(m_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_device_actions, SLOT(changeDevice(QTreeWidgetItem*))); m_device_stack->loadData(devices); m_tree->loadData(devices); connect(m_tree, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), m_device_actions, SLOT(changeDevice(QTreeWidgetItem*))); m_device_actions->changeDevice(m_tree->currentItem()); readConfig(); } QScrollArea *DeviceTab::setupPropertyStack() { QScrollArea *device_scroll = new QScrollArea(); device_scroll->setFrameStyle(QFrame::NoFrame); device_scroll->setBackgroundRole(QPalette::Base); device_scroll->setAutoFillBackground(true); device_scroll->setWidget(m_device_stack); device_scroll->setWidgetResizable(true); return device_scroll; } void DeviceTab::deviceContextMenu(QTreeWidgetItem *item) { m_device_actions->changeDevice(item); QMenu *menu = new DeviceMenu(m_device_actions, this); menu->exec(QCursor::pos()); menu->deleteLater(); } void DeviceTab::readConfig() { KConfigSkeleton skeleton; bool show_toolbars, show_device_barchart; QString icon_size, icon_text; skeleton.setCurrentGroup("General"); skeleton.addItemBool("show_device_barchart", show_device_barchart, true); skeleton.addItemBool("show_toolbars", show_toolbars, true); skeleton.addItemString("toolbar_icon_size", icon_size, "mediumicons"); skeleton.addItemString("toolbar_icon_text", icon_text, "iconsonly"); if (show_device_barchart) m_size_chart->show(); else m_size_chart->hide(); if (show_toolbars) toolBar("devicetoolbar")->show(); else toolBar("devicetoolbar")->hide(); QSize size(22, 22); if (icon_size == "smallicons") size = QSize(16, 16); else if (icon_size == "mediumicons") size = QSize(22, 22); else if (icon_size == "largeicons") size = QSize(32, 32); else if (icon_size == "hugeicons") size = QSize(48, 48); toolBar("devicetoolbar")->setIconSize(size); if (icon_text == "iconsonly") toolBar("devicetoolbar")->setToolButtonStyle(Qt::ToolButtonIconOnly); else if (icon_text == "textonly") toolBar("devicetoolbar")->setToolButtonStyle(Qt::ToolButtonTextOnly); else if (icon_text == "textbesideicons") toolBar("devicetoolbar")->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); else if (icon_text == "textundericons") toolBar("devicetoolbar")->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } KToolBar* DeviceTab::buildDeviceToolBar() { KToolBar *const toolbar = toolBar("devicetoolbar"); toolbar->setContextMenuPolicy(Qt::PreventContextMenu); toolbar->addAction(m_device_actions->action("tablecreate")); toolbar->addSeparator(); toolbar->addAction(m_device_actions->action("partremove")); toolbar->addAction(m_device_actions->action("partadd")); toolbar->addAction(m_device_actions->action("partchange")); toolbar->addAction(m_device_actions->action("changeflags")); toolbar->addAction(m_device_actions->action("max_pv")); toolbar->addSeparator(); toolbar->addAction(m_device_actions->action("vgcreate")); toolbar->addAction(m_device_actions->action("vgreduce")); toolbar->addSeparator(); toolbar->addAction(m_device_actions->action("mount")); toolbar->addAction(m_device_actions->action("unmount")); toolbar->addSeparator(); toolbar->addAction(m_device_actions->action("max_fs")); toolbar->addAction(m_device_actions->action("fsck")); toolbar->addSeparator(); toolbar->addAction(m_device_actions->action("mkfs")); return toolbar; } kvpm-0.9.10/kvpm/processprogress.cpp0000644000175000017500000001406712770324126017742 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "processprogress.h" #include "executablefinder.h" #include "topwindow.h" #include "progressbox.h" #include #include #include #include #include #include #include #include #include ProcessProgress::ProcessProgress(QStringList arguments, const bool allowCancel, QObject *parent) : QObject(parent) { m_exit_code = 127; // command not found m_progress_dialog = nullptr; if (arguments.size() == 0) { qDebug() << "ProcessProgress given an empty arguments list"; } else { const QString executable = arguments.takeFirst(); const QString executable_path = ExecutableFinder::getPath(executable); if (!executable_path.isEmpty()) { qApp->setOverrideCursor(Qt::WaitCursor); m_process = new QProcess(this); QStringList environment = QProcess::systemEnvironment(); environment << "LVM_SUPPRESS_FD_WARNINGS=1"; m_process->setEnvironment(environment); m_loop = new QEventLoop(this); if (allowCancel) { TopWindow::getProgressBox()->hide(); m_progress_dialog = new QProgressDialog(nullptr, Qt::CustomizeWindowHint | Qt::WindowTitleHint); m_progress_dialog->setLabelText(i18n("Running program: %1", executable)); m_progress_dialog->setCancelButtonText(i18n("Cancel")); m_progress_dialog->setMinimumDuration(250); m_progress_dialog->setRange(0, 0); m_progress_dialog->setModal(true); connect(m_progress_dialog, SIGNAL(rejected()), this, SLOT(cancelProcess())); } else { m_progress_dialog = nullptr; TopWindow::getProgressBox()->setRange(0, 0); TopWindow::getProgressBox()->setText(executable); } connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(cleanup(int, QProcess::ExitStatus))); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStandardOut())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStandardError())); m_process->setProcessChannelMode(QProcess::SeparateChannels); m_process->setReadChannel(QProcess::StandardOutput); m_process->setProgram(executable_path); m_process->setArguments(arguments); m_process->start(); m_process->closeWriteChannel(); if (allowCancel) m_loop->exec(QEventLoop::AllEvents); else m_loop->exec(QEventLoop::ExcludeUserInputEvents); } else { KMessageBox::error(nullptr, i18n("Executable: '%1' not found", executable)); } } } void ProcessProgress::cleanup(const int code, const QProcess::ExitStatus status) { m_exit_code = code; m_loop->exit(); bool cancelled = false; qApp->restoreOverrideCursor(); if (m_progress_dialog != nullptr) { m_progress_dialog->close(); cancelled = m_progress_dialog->wasCanceled(); delete m_progress_dialog; } if (m_exit_code || (status == QProcess::CrashExit)) { if ((m_exit_code == 0) && (status == QProcess::CrashExit)) m_exit_code = 1; // if it crashed without an exit code, set a non zero exit code const QString errors = m_output_all.join(""); if (status != QProcess::CrashExit || cancelled) KMessageBox::error(nullptr, i18n("%1 produced this output: %2", m_process->program(), errors)); else KMessageBox::error(nullptr, i18n("%1 crashed with this output: %2", m_process->program(), errors)); } TopWindow::getProgressBox()->reset(); } QStringList ProcessProgress::programOutput() { return m_output_no_error; } QStringList ProcessProgress::programOutputAll() { return m_output_all; } int ProcessProgress::exitCode() { return m_exit_code; } void ProcessProgress::cancelProcess() { const QString warning = i18n("Really kill process %1", m_process->program()); kill(m_process->pid(), SIGSTOP); if (KMessageBox::warningYesNo(nullptr, warning, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { m_process->kill(); m_progress_dialog->show(); m_progress_dialog->setLabelText(i18n("Waiting for process to finish")); m_progress_dialog->setCancelButtonText(QString()); } else if (m_process->state() == QProcess::Running) { m_progress_dialog->show(); kill(m_process->pid(), SIGCONT); } } void ProcessProgress::readStandardOut() { QString output_line; m_process->setReadChannel(QProcess::StandardOutput); while (m_process->canReadLine()) { output_line = m_process->readLine(); if (!output_line.contains("partial mode.", Qt::CaseInsensitive)) { m_output_no_error << output_line; } m_output_all << output_line; } } void ProcessProgress::readStandardError() { QString output_line; m_process->setReadChannel(QProcess::StandardError); while (m_process->canReadLine()) { output_line = m_process->readLine(); m_output_all << output_line; } } kvpm-0.9.10/kvpm/mount.cpp0000644000175000017500000004523412770324126015641 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "mount.h" #include "logvol.h" #include "mountentry.h" #include "mounttables.h" #include "storagepartition.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const int BUFF_LEN = 2000; // Enough? MountDialog::MountDialog(LogVol *const volume, QWidget *parent) : KvpmDialog(parent) { m_device_to_mount = volume->getMapperPath(); m_filesystem_type = volume->getFilesystem(); m_is_writable = volume->isWritable(); m_mount_point = volume->getFstabMountPoint(); buildDialog(); } MountDialog::MountDialog(StoragePartition *const partition, QWidget *parent) : KvpmDialog(parent) { m_device_to_mount = partition->getName(); m_filesystem_type = partition->getFilesystem(); m_is_writable = partition->isWritable(); m_mount_point = partition->getFstabMountPoint(); buildDialog(); } void MountDialog::buildDialog() { setCaption("Mount a Filesystem"); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); QLabel *const device_label = new QLabel(i18n("Device to mount: %1", m_device_to_mount)); device_label->setAlignment(Qt::AlignCenter); layout->addWidget(device_label); layout->addSpacing(5); QTabWidget *const tab_widget = new QTabWidget(); tab_widget->addTab(mainTab(), i18n("Main")); tab_widget->addTab(optionsTab(), i18n("Options")); layout->addWidget(tab_widget); } QWidget* MountDialog::filesystemBox() { QGroupBox *const filesystem_box = new QGroupBox(i18n("Filesystem Type"), this); QVBoxLayout *const layout_left = new QVBoxLayout(); QVBoxLayout *const layout_center = new QVBoxLayout(); QVBoxLayout *const layout_right = new QVBoxLayout(); QHBoxLayout *const upper_layout = new QHBoxLayout(); QHBoxLayout *const lower_layout = new QHBoxLayout(); QVBoxLayout *const filesystem_layout = new QVBoxLayout(); upper_layout->addLayout(layout_left); upper_layout->addLayout(layout_center); upper_layout->addLayout(layout_right); filesystem_layout->addLayout(upper_layout); filesystem_layout->addLayout(lower_layout); filesystem_box->setLayout(filesystem_layout); ext2_button = new QRadioButton("ext2", this); ext3_button = new QRadioButton("ext3", this); ext4_button = new QRadioButton("ext4", this); btrfs_button = new QRadioButton("btrfs", this); reiserfs3_button = new QRadioButton("reiser3", this); reiserfs4_button = new QRadioButton("reiser4", this); xfs_button = new QRadioButton("xfs", this); jfs_button = new QRadioButton("jfs", this); vfat_button = new QRadioButton("vfat", this); udf_button = new QRadioButton("udf", this); iso9660_button = new QRadioButton("iso9660", this); hfs_button = new QRadioButton("hfs", this); ntfs_button = new QRadioButton("ntfs", this); specify_button = new QRadioButton(i18n("Specify another fileystem:"), this); if (m_filesystem_type == "ext2") ext2_button->setChecked(true); else if (m_filesystem_type == "ext3") ext3_button->setChecked(true); else if (m_filesystem_type == "ext4") ext4_button->setChecked(true); else if (m_filesystem_type == "btrfs") btrfs_button->setChecked(true); else if (m_filesystem_type == "reiserfs") reiserfs3_button->setChecked(true); else if (m_filesystem_type == "reiser4") reiserfs4_button->setChecked(true); else if (m_filesystem_type == "xfs") xfs_button->setChecked(true); else if (m_filesystem_type == "jfs") jfs_button->setChecked(true); else if (m_filesystem_type == "vfat") vfat_button->setChecked(true); else if (m_filesystem_type == "udf") udf_button->setChecked(true); else if (m_filesystem_type == "iso9660") iso9660_button->setChecked(true); else if (m_filesystem_type == "hfs") hfs_button->setChecked(true); else if (m_filesystem_type == "ntfs") ntfs_button->setChecked(true); else specify_button->setChecked(true); m_filesystem_edit = new QLineEdit(m_filesystem_type); if (specify_button->isChecked() && (m_filesystem_edit->text()).isEmpty()) (button(KDialog::Ok))->setEnabled(false); layout_left->addWidget(ext2_button); layout_left->addWidget(ext3_button); layout_left->addWidget(ext4_button); layout_left->addWidget(btrfs_button); layout_left->addWidget(ntfs_button); layout_left->addStretch(); layout_center->addWidget(reiserfs3_button); layout_center->addWidget(reiserfs4_button); layout_center->addWidget(xfs_button); layout_center->addWidget(jfs_button); layout_center->addStretch(); layout_right->addWidget(udf_button); layout_right->addWidget(iso9660_button); layout_right->addWidget(hfs_button); layout_right->addWidget(vfat_button); layout_right->addStretch(); lower_layout->addWidget(specify_button); lower_layout->addWidget(m_filesystem_edit); connect(m_filesystem_edit, SIGNAL(textEdited(const QString)), this, SLOT(toggleOKButton())); connect(specify_button, SIGNAL(toggled(bool)), this, SLOT(toggleOKButton())); return filesystem_box; } QWidget* MountDialog::optionsTab() { QWidget *const options = new QWidget(); QVBoxLayout *const options_layout = new QVBoxLayout(); options->setLayout(options_layout); QGroupBox *const common_options_box = new QGroupBox(i18n("Common Options"), this); QVBoxLayout *const layout_left = new QVBoxLayout(); QVBoxLayout *const layout_right = new QVBoxLayout(); QHBoxLayout *const common_options_layout = new QHBoxLayout(); common_options_layout->addLayout(layout_left); common_options_layout->addLayout(layout_right); common_options_box->setLayout(common_options_layout); sync_check = new QCheckBox("sync"); dirsync_check = new QCheckBox("dirsync"); rw_check = new QCheckBox("rw"); suid_check = new QCheckBox("suid"); dev_check = new QCheckBox("dev"); exec_check = new QCheckBox("exec"); mand_check = new QCheckBox("mand"); acl_check = new QCheckBox("acl"); user_xattr_check = new QCheckBox("user xattr"); if (m_is_writable) { rw_check->setChecked(true); } else { rw_check->setChecked(false); rw_check->setEnabled(false); } suid_check->setChecked(true); dev_check->setChecked(true); exec_check->setChecked(true); sync_check->setToolTip(i18n("Always use synchronous I/O")); rw_check->setToolTip(i18n("Allow writing in addition to reading")); suid_check->setToolTip(i18n("Allow the suid bit to have effect")); dev_check->setToolTip(i18n("Allow the use of block and special devices")); exec_check->setToolTip(i18n("Allow the execution of binary files")); mand_check->setToolTip(i18n("Allow manditory file locks")); acl_check->setToolTip(i18n("Allow use of access control lists")); layout_left->addWidget(rw_check); layout_left->addWidget(suid_check); layout_left->addWidget(sync_check); layout_left->addWidget(dirsync_check); layout_left->addWidget(acl_check); layout_left->addStretch(); layout_right->addWidget(dev_check); layout_right->addWidget(exec_check); layout_right->addWidget(mand_check); layout_right->addWidget(user_xattr_check); layout_right->addStretch(); QHBoxLayout *options_atime_layout = new QHBoxLayout(); options_layout->addLayout(options_atime_layout); QGroupBox *atime_box = new QGroupBox(i18n("Update atime"), this); QVBoxLayout *atime_layout = new QVBoxLayout(); atime_box->setLayout(atime_layout); options_atime_layout->addWidget(common_options_box); QVBoxLayout *atime_journal_layout = new QVBoxLayout(); options_atime_layout->addLayout(atime_journal_layout); m_filesystem_journal_box = new QGroupBox(i18n("Journaling")); QVBoxLayout *journaling_layout = new QVBoxLayout; m_filesystem_journal_box->setLayout(journaling_layout); data_journal_button = new QRadioButton("data=journal"); data_ordered_button = new QRadioButton("data=ordered"); data_ordered_button->setChecked(true); data_writeback_button = new QRadioButton("data=writeback"); journaling_layout->addWidget(data_journal_button); journaling_layout->addWidget(data_ordered_button); journaling_layout->addWidget(data_writeback_button); atime_journal_layout->addWidget(m_filesystem_journal_box); atime_button = new QRadioButton("strict atime"); noatime_button = new QRadioButton("noatime"); relatime_button = new QRadioButton("relatime"); relatime_button->setChecked(true); nodiratime_check = new QCheckBox("nodiratime"); atime_layout->addWidget(atime_button); atime_layout->addWidget(noatime_button); atime_layout->addWidget(relatime_button); atime_layout->addSpacing(5); atime_layout->addWidget(nodiratime_check); atime_button->setToolTip(i18n("Always update atime")); noatime_button->setToolTip(i18n("Do not update atime")); relatime_button->setToolTip(i18n("Access time is only updated if the previous " "access time was earlier than the current modify " "or change time")); nodiratime_check->setToolTip(i18n("Do not update atime for directory access")); atime_journal_layout->addWidget(atime_box); QGroupBox *filesystem_options_box = new QGroupBox(i18n("Filesystem specific mount options")); m_fs_specific_edit = new QLineEdit(); m_fs_specific_edit->setPlaceholderText(i18n("comma separated list of additional mount options")); options_layout->addWidget(filesystem_options_box); QVBoxLayout *filesystem_options_box_layout = new QVBoxLayout(); filesystem_options_box->setLayout(filesystem_options_box_layout); filesystem_options_box_layout->addWidget(m_fs_specific_edit); toggleAdditionalOptions(true); connect(ext2_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(ext3_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(ext4_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(reiserfs3_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(reiserfs4_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(btrfs_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(xfs_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(jfs_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(vfat_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(ntfs_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(specify_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(udf_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(iso9660_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); connect(hfs_button, SIGNAL(toggled(bool)), this, SLOT(toggleAdditionalOptions(bool))); return options; } QWidget* MountDialog::mainTab() { QWidget *const main = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); layout->addWidget(filesystemBox()); layout->addStretch(); layout->addWidget(mountPointBox()); main->setLayout(layout); return main; } QWidget* MountDialog::mountPointBox() { QGroupBox *const mount_point_box = new QGroupBox(i18n("Mount Point")); QHBoxLayout *const mount_point_layout = new QHBoxLayout(); mount_point_box->setLayout(mount_point_layout); m_mount_point_edit = new QLineEdit(m_mount_point); if ((m_mount_point_edit->text()).isEmpty()) (button(KDialog::Ok))->setEnabled(false); mount_point_layout->addWidget(m_mount_point_edit); QPushButton *const browse_button = new QPushButton(i18n("Browse"), this); mount_point_layout->addWidget(browse_button); connect(m_mount_point_edit, SIGNAL(textChanged(const QString)), this, SLOT(toggleOKButton())); connect(browse_button, SIGNAL(clicked(bool)), this, SLOT(selectMountPoint(bool))); return mount_point_box; } void MountDialog::selectMountPoint(bool) { const QString filter = "*"; QString start; if (!m_mount_point.isEmpty()) start = QString("file://" + m_mount_point); else start = QString("file:///"); m_mount_point = QFileDialog::getExistingDirectory(nullptr, QString(), start); if (m_mount_point.length() > 1 && m_mount_point.endsWith('/')) m_mount_point.chop(1); // remove trailing "/" character m_mount_point_edit->setText(m_mount_point); } void MountDialog::commit() { unsigned long options = 0; QStringList standard_options, additional_options; QString all_options; if (ext2_button->isChecked()) m_filesystem_type = "ext2"; else if (ext3_button->isChecked()) m_filesystem_type = "ext3"; else if (ext4_button->isChecked()) m_filesystem_type = "ext4"; else if (btrfs_button->isChecked()) m_filesystem_type = "btrfs"; else if (reiserfs3_button->isChecked()) m_filesystem_type = "reiserfs"; else if (reiserfs4_button->isChecked()) m_filesystem_type = "reiser4"; else if (xfs_button->isChecked()) m_filesystem_type = "xfs"; else if (jfs_button->isChecked()) m_filesystem_type = "jfs"; else if (vfat_button->isChecked()) m_filesystem_type = "vfat"; else if (ntfs_button->isChecked()) m_filesystem_type = "ntfs"; else m_filesystem_type = m_filesystem_edit->text(); m_mount_point = m_mount_point_edit->text(); if (m_mount_point.length() > 1 && m_mount_point.endsWith('/')) // remove trailing slash m_mount_point.chop(1); if (sync_check->isChecked()) { standard_options.append("sync"); options |= MS_SYNCHRONOUS; } if (dirsync_check->isChecked()) { standard_options.append("dirsync"); options |= MS_DIRSYNC; } if (rw_check->isChecked()) { standard_options.append("rw"); } else { standard_options.append("ro"); options |= MS_RDONLY; } if (!suid_check->isChecked()) { standard_options.append("nosuid"); options |= MS_NOSUID; } if (!dev_check->isChecked()) { standard_options.append("nodev"); options |= MS_NODEV; } if (!exec_check->isChecked()) { standard_options.append("noexec"); options |= MS_NOEXEC; } if (mand_check->isChecked()) { standard_options.append("mand"); options |= MS_MANDLOCK; } if (atime_button->isChecked()) { standard_options.append("atime"); options |= MS_STRICTATIME; } else if (noatime_button->isChecked()) { standard_options.append("noatime"); options |= MS_NOATIME; } else if (relatime_button->isChecked()) { standard_options.append("relatime"); options |= MS_RELATIME; } if (nodiratime_check->isChecked()) { standard_options.append("nodiratime"); options |= MS_NODIRATIME; } /* "data=ordered" is the default so we ignore that button */ if (m_filesystem_journal_box->isEnabled()) { if (data_journal_button->isChecked()) additional_options.append("data=journal"); else if (data_writeback_button->isChecked()) additional_options.append("data=writeback"); } if (acl_check->isEnabled()) { if (acl_check->isChecked()) additional_options.append("acl"); else additional_options.append("noacl"); } if (user_xattr_check->isEnabled()) { if (user_xattr_check->isChecked()) additional_options.append("user_xattr"); else additional_options.append("nouser_xattr"); } if (!m_fs_specific_edit->text().trimmed().isEmpty()) additional_options.append((m_fs_specific_edit->text()).trimmed()); all_options = standard_options.join(","); if (additional_options.size()) { all_options.append(","); all_options.append(additional_options.join(",")); } const QByteArray device = m_device_to_mount.toLocal8Bit(); const QByteArray mount_point = m_mount_point.toLocal8Bit(); const QByteArray fs_type = m_filesystem_type.toLocal8Bit(); const QByteArray fs_options = additional_options.join(",").toLocal8Bit(); const int error = mount(device.data(), mount_point.data(), fs_type.data(), options, fs_options.data()); if (error) KMessageBox::error(nullptr, QString("Error number: %1 %2").arg(errno).arg(strerror(errno))); else MountTables::addEntry(m_device_to_mount, m_mount_point, m_filesystem_type, all_options, 0, 0); } void MountDialog::toggleOKButton() { if ((specify_button->isChecked() && (m_filesystem_edit->text()).isEmpty()) || (m_mount_point_edit->text()).isEmpty()) (button(KDialog::Ok))->setEnabled(false); else (button(KDialog::Ok))->setEnabled(true); } void MountDialog::toggleAdditionalOptions(bool) { if (ext3_button->isChecked()) m_filesystem_journal_box->setEnabled(true); else m_filesystem_journal_box->setEnabled(false); if (ext2_button->isChecked() || ext3_button->isChecked() || ext4_button->isChecked() || reiserfs3_button->isChecked() || reiserfs4_button->isChecked()) { acl_check->setEnabled(true); acl_check->setChecked(true); user_xattr_check->setEnabled(true); user_xattr_check->setChecked(true); } else { acl_check->setEnabled(false); acl_check->setChecked(false); user_xattr_check->setEnabled(false); user_xattr_check->setChecked(false); } } kvpm-0.9.10/kvpm/lvactions.h0000644000175000017500000000177012770324126016143 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVACTIONS_H #define LVACTIONS_H #include class QAction; class QTreeWidgetItem; class LogVol; class VolGroup; class LVActions : public KActionCollection { Q_OBJECT VolGroup *m_vg = nullptr; LogVol *m_lv = nullptr; int m_segment = 0; void setActions(LogVol *const lv, const int segment); void setMirrorActions(LogVol *const lv); public: explicit LVActions(VolGroup *const group, QWidget *parent = nullptr); public slots: void changeLv(QTreeWidgetItem *item); void changeLv(LogVol *lv, int segment); private slots: void callDialog(QAction *action); }; #endif kvpm-0.9.10/kvpm/executablefinder.cpp0000644000175000017500000000643012770324126020003 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "executablefinder.h" #include #include #include QMap ExecutableFinder::m_path_map = QMap(); // Static initialization /* The purpose of this class is to map the name of a program with the full path of the executable */ ExecutableFinder::ExecutableFinder(QObject *parent) : QObject(parent) { m_keys << "dumpe2fs" << "fsck" << "lvchange" << "lvconvert" << "lvcreate" << "lvextend" << "lvm" << "lvreduce" << "lvremove" << "lvrename" << "lvs" << "mkfs" << "mkswap" << "ntfsresize" << "pvchange" << "pvcreate" << "pvmove" << "pvremove" << "pvresize" << "pvs" << "resize2fs" << "resize_reiserfs" << "udevadm" << "vgchange" << "vgcreate" << "vgexport" << "vgextend" << "vgimport" << "vgmerge" << "vgreduce" << "vgremove" << "vgrename" << "vgs" << "vgsplit" << "wipefs" << "xfs_growfs"; m_default_search_paths << "/sbin/" << "/usr/sbin/" << "/bin/" << "/usr/bin/" << "/usr/local/bin/" << "/usr/local/sbin/"; reload(); } QString ExecutableFinder::getPath(const QString name) { QString path = m_path_map.value(name); if (path.isEmpty()) qDebug() << "Excutable Finder: error " << name << " does not map to any path"; return path; } void ExecutableFinder::reload() { KConfigSkeleton skeleton; QStringList search; skeleton.setCurrentGroup("SystemPaths"); skeleton.addItemStringList("SearchPath", search, m_default_search_paths); reload(search); } void ExecutableFinder::reload(QStringList search) { KDE_struct_stat buf; const int key_length = m_keys.size(); m_path_map.clear(); for (int y = 0; y < key_length; y++) { for (int x = 0; x < search.size(); x++) { QByteArray path_qba = QString(search[x] + m_keys[y]).toLocal8Bit(); const char *path = path_qba.data(); if (KDE_lstat(path, &buf) == 0) { m_path_map.insert(m_keys[y], search[x] + m_keys[y]); break; } } } m_not_found.clear(); for (int x = 0; x < key_length; x++) { if ((m_path_map.value(m_keys[x])).isEmpty()) m_not_found.append(m_keys[x]); } } QStringList ExecutableFinder::getAllPaths() { return m_path_map.values(); } QStringList ExecutableFinder::getAllNames() { return m_path_map.keys(); } QStringList ExecutableFinder::getNotFound() { return m_not_found; } kvpm-0.9.10/kvpm/pedexceptions.h0000644000175000017500000000101012770324126016776 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PEDEXCEPTIONS_H #define PEDEXCEPTIONS_H #include PedExceptionOption my_handler(PedException *exception); #endif kvpm-0.9.10/kvpm/lvcreate.cpp0000644000175000017500000013406312770324126016303 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvcreate.h" #include "fsextend.h" #include "logvol.h" #include "lvmconfig.h" #include "misc.h" #include "mountentry.h" #include "physvol.h" #include "pvgroupbox.h" #include "processprogress.h" #include "sizeselectorbox.h" #include "volgroup.h" #include #include #include #include #include #include #include /* This class handles both the creation and extension of logical volumes and snapshots since both processes are so similar. */ // Creating a new volume or thin pool LVCreateDialog::LVCreateDialog(VolGroup *const vg, const bool ispool, QWidget *parent) : LvCreateDialogBase(vg, -1, false, false, false, ispool, QString(""), QString(""), parent), m_ispool(ispool) { m_lv = nullptr; m_extend = false; m_snapshot = false; if (hasInitialErrors()) preventExec(); else buildDialog(); } // Extending an existing volume or creating a snapshot LVCreateDialog::LVCreateDialog(LogVol *const volume, const bool snapshot, QWidget *parent) : LvCreateDialogBase(volume->getVg(), (volume->isCowSnap() || volume->isThinPool()) ? -1 : fs_max_extend(volume->getMapperPath(), volume->getFilesystem(), volume->isMounted()), !snapshot, snapshot, false, volume->isThinPool(), volume->getName(), QString(""), parent), m_ispool(volume->isThinPool()), m_snapshot(snapshot), m_extend(!snapshot), m_lv(volume) { if (hasInitialErrors()) preventExec(); else buildDialog(); } void LVCreateDialog::buildDialog() { const VolGroup *const vg = getVg(); if (m_extend) initializeSizeSelector(vg->getExtentSize(), m_lv->getExtents(), vg->getAllocatableExtents() + m_lv->getExtents()); else initializeSizeSelector(vg->getExtentSize(), 0, vg->getAllocatableExtents()); m_physical_tab = createPhysicalTab(); setPhysicalTab(m_physical_tab); enableTypeOptions(m_type_combo->currentIndex()); enableStripeCombo(m_stripe_count_spin->value()); makeConnections(); setMaxSize(); resetOkButton(); if (!m_snapshot && !m_extend && !m_ispool) setZero(true); else if (m_ispool) setZero(true); } void LVCreateDialog::makeConnections() { connect(m_pv_box, SIGNAL(stateChanged()), this, SLOT(setMaxSize())); connect(m_stripe_count_spin, SIGNAL(valueChanged(int)), this, SLOT(enableStripeCombo(int))); connect(m_stripe_count_spin, SIGNAL(valueChanged(int)), this, SLOT(setMaxSize())); connect(m_mirror_count_spin, SIGNAL(valueChanged(int)), this, SLOT(setMaxSize())); connect(m_type_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(enableTypeOptions(int))); connect(m_type_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(setMaxSize())); connect(m_type_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(enableMonitoring(int))); connect(m_log_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(setMaxSize())); connect(this, SIGNAL(extendFs()), this, SLOT(setMaxSize())); } QWidget* LVCreateDialog::createPhysicalTab() { QVBoxLayout *const layout = new QVBoxLayout; m_physical_tab = new QWidget(this); m_physical_tab->setLayout(layout); QList> pv_space_list = getPvSpaceList(); m_pv_box = new PvGroupBox(pv_space_list, INHERIT_NORMAL, getVg()->getPolicy()); layout->addWidget(m_pv_box); QHBoxLayout *const lower_h_layout = new QHBoxLayout; QVBoxLayout *const lower_v_layout = new QVBoxLayout; layout->addLayout(lower_h_layout); lower_h_layout->addStretch(); lower_h_layout->addLayout(lower_v_layout); lower_h_layout->addStretch(); m_volume_box = new QGroupBox(); QVBoxLayout *const volume_layout = new QVBoxLayout; m_volume_box->setLayout(volume_layout); volume_layout->addWidget(createTypeWidget(pv_space_list.size())); m_stripe_widget = createStripeWidget(); m_mirror_widget = createMirrorWidget(pv_space_list.size()); if (m_ispool && !m_extend) { volume_layout->addWidget(createChunkWidget()); connect(m_chunk_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(setMaxSize())); } volume_layout->addWidget(m_stripe_widget); volume_layout->addWidget(m_mirror_widget); volume_layout->addStretch(); lower_v_layout->addWidget(m_volume_box); return m_physical_tab; } int LVCreateDialog::getChunkSize() // returns pool chunk size in bytes { int chunk = 0x10000; // 64KiB if (m_extend) { chunk = m_lv->getChunkSize(0); // if chunk size can be different across segments this will need to be changed } else if (m_chunk_combo->currentIndex() > 0) { chunk = QVariant(m_chunk_combo->itemData(m_chunk_combo->currentIndex(), Qt::UserRole)).toInt(); } else { long long meta = (64 * getSelectorExtents() * getVg()->getExtentSize()) / chunk; while ((meta > 0x8000000) && (chunk < 0x40000000)) { // meta > 128MiB and chunk < 1GB chunk *= 2; meta /= 2; } } return chunk; } int LVCreateDialog::getChunkSize(long long volumeSize) // returns pool chunk size in bytes { int chunk = 0x10000; // 64KiB if (m_extend) { chunk = m_lv->getChunkSize(0); // if chunk size can be different across segments this will need to be changed } else if (m_chunk_combo->currentIndex() > 0) { chunk = QVariant(m_chunk_combo->itemData(m_chunk_combo->currentIndex(), Qt::UserRole)).toInt(); } else { long long meta = (64 * volumeSize) / chunk; while ((meta > 0x8000000) && (chunk < 0x40000000)) { // meta > 128MiB and chunk < 1GB chunk *= 2; meta /= 2; } } return chunk; } QWidget* LVCreateDialog::createChunkWidget() { QWidget *const widget = new QWidget; QHBoxLayout *const layout = new QHBoxLayout; layout->addWidget(new QLabel(i18n("Chunk size: "))); m_chunk_combo = new QComboBox(); m_chunk_combo->addItem(i18n("default")); layout->addWidget(m_chunk_combo); unsigned int chunk; for (int n = 0; n < 15; n++) { chunk = round(64 * pow(2, n)); if (chunk < 1000) m_chunk_combo->addItem(QString("%1").arg(chunk) + " KiB"); else m_chunk_combo->addItem(QString("%1").arg(chunk/1024) + " MiB"); m_chunk_combo->setItemData(n + 1, QVariant(chunk * 1024), Qt::UserRole); // chunk size in bytes } widget->setLayout(layout); return widget; } QWidget* LVCreateDialog::createStripeWidget() { QWidget *const widget = new QWidget; QVBoxLayout *const layout = new QVBoxLayout; m_stripe_size_combo = new QComboBox(); m_stripe_size_combo->setEnabled(false); for (int n = 2; (round(pow(2, n) * 1024)) <= getVg()->getExtentSize() ; n++) { m_stripe_size_combo->addItem(QString("%1").arg(round(pow(2, n))) + " KiB"); m_stripe_size_combo->setItemData(n - 2, QVariant(round(pow(2, n))), Qt::UserRole); m_stripe_size_combo->setEnabled(true); // only enabled if the combo box has at least one entry! if ((n - 2) < 5) m_stripe_size_combo->setCurrentIndex(n - 2); } QLabel *const size_label = new QLabel(i18n("Stripe Size: ")); size_label->setBuddy(m_stripe_size_combo); m_stripe_count_spin = new QSpinBox(); m_stripe_count_spin->setEnabled(m_stripe_size_combo->isEnabled()); m_stripe_count_spin->setRange(1, getMaxStripes()); m_stripe_count_spin->setSpecialValueText(i18n("none")); if (m_stripe_size_combo->isEnabled()) { QHBoxLayout *const size_layout = new QHBoxLayout; size_layout->addWidget(size_label); size_layout->addWidget(m_stripe_size_combo); QLabel *const count_label = new QLabel(i18n("Number of stripes: ")); count_label->setBuddy(m_stripe_count_spin); QHBoxLayout *const count_layout = new QHBoxLayout; count_layout->addWidget(count_label); count_layout->addWidget(m_stripe_count_spin); layout->addLayout(size_layout); layout->addLayout(count_layout); } else { QLabel *const error_label = new QLabel(i18n("Extents smaller than 4KiB can not be striped")); layout->addWidget(error_label); for (int x = m_type_combo->count() - 1; x > 2; x--) m_type_combo->removeItem(x); } widget->setLayout(layout); /* If we are extending a volume we try to match the striping of the last segment of that volume, if it was striped */ if (m_extend) { int seg_count = 1; int stripe_count = 1; int stripe_size = 4; LvList logvols; if (m_lv->isLvmMirror()) { // Tries to match striping to last segment of first leg logvols = m_lv->getAllChildrenFlat(); for (int x = 0; x < logvols.size(); x++) { if (logvols[x]->isLvmMirrorLeg() && !(logvols[x]->isLvmMirrorLog())) { seg_count = logvols[x]->getSegmentCount(); stripe_count = logvols[x]->getSegmentStripes(seg_count - 1); stripe_size = logvols[x]->getSegmentStripeSize(seg_count - 1); break; } } } else { seg_count = m_lv->getSegmentCount(); stripe_count = m_lv->getSegmentStripes(seg_count - 1); stripe_size = m_lv->getSegmentStripeSize(seg_count - 1); if (m_lv->isRaid()) { m_stripe_count_spin->setEnabled(false); if (m_lv->getRaidType() == 4 || m_lv->getRaidType() == 5){ stripe_count -= 1; m_stripe_count_spin->setSuffix(i18n(" + 1 parity")); m_stripe_count_spin->setSpecialValueText(""); } else if (m_lv->getRaidType() == 6) { stripe_count -= 2; m_stripe_count_spin->setSuffix(i18n(" + 2 parity")); m_stripe_count_spin->setSpecialValueText(""); } } } m_stripe_count_spin->setValue(stripe_count); if (stripe_count > 1) { int stripe_index = m_stripe_size_combo->findData(QVariant(stripe_size / 1024)); if (stripe_index == -1) stripe_index = 0; m_stripe_size_combo->setCurrentIndex(stripe_index); } } return widget; } QWidget* LVCreateDialog::createMirrorWidget(int pvcount) { QWidget *const widget = new QWidget; m_log_combo = new QComboBox; m_log_combo->addItem(i18n("Mirrored disk based log")); m_log_combo->addItem(i18n("Single disk based log")); m_log_combo->addItem(i18n("Memory based log")); QHBoxLayout *const log_layout = new QHBoxLayout(); QLabel *const log_label = new QLabel(i18n("Mirror log: ")); log_label->setBuddy(m_log_combo); log_layout->addWidget(log_label); log_layout->addWidget(m_log_combo); QHBoxLayout *const spin_layout = new QHBoxLayout(); m_mirror_count_spin = new QSpinBox(); m_mirror_count_spin->setRange(1, pvcount); if (m_extend) { if (m_lv->isLvmMirror() || m_lv->getRaidType() == 1) m_mirror_count_spin->setValue(m_lv->getMirrorCount()); else m_mirror_count_spin->setValue(1); if (m_lv->isLvmMirror()) { if (m_lv->getLogCount() == 0) m_log_combo->setCurrentIndex(2); else if (m_lv->getLogCount() == 1) m_log_combo->setCurrentIndex(1); else if (m_lv->getLogCount() == 2) m_log_combo->setCurrentIndex(0); } else { m_log_combo->setCurrentIndex(1); } m_log_combo->setEnabled(false); m_mirror_count_spin->setEnabled(false); } QLabel *const count_label = new QLabel(i18n("Number of mirror legs: ")); count_label->setBuddy(m_mirror_count_spin); spin_layout->addWidget(count_label); spin_layout->addWidget(m_mirror_count_spin); QVBoxLayout *const layout = new QVBoxLayout; layout->addLayout(log_layout); layout->addLayout(spin_layout); widget->setLayout(layout); return widget; } QWidget* LVCreateDialog::createTypeWidget(int pvcount) { QWidget *const widget = new QWidget; m_type_combo = new QComboBox(); m_type_combo->addItem(i18n("Linear")); if (m_extend) { const bool ismirror = m_lv->isMirror(); const bool israid = m_lv->isRaid(); const int raidtype = m_lv->getRaidType(); m_type_combo->setEnabled(false); m_type_combo->addItem(i18n("LVM2 Mirror")); m_type_combo->addItem(i18n("RAID 1 Mirror")); m_type_combo->addItem(i18n("RAID 4")); m_type_combo->addItem(i18n("RAID 5")); m_type_combo->addItem(i18n("RAID 6")); if (ismirror && !israid) { m_type_combo->setCurrentIndex(1); } else if (israid){ if (raidtype == 1) m_type_combo->setCurrentIndex(2); else if (raidtype == 4) m_type_combo->setCurrentIndex(3); else if (raidtype == 5) m_type_combo->setCurrentIndex(4); else if (raidtype == 6) m_type_combo->setCurrentIndex(5); else m_type_combo->setCurrentIndex(0); } else { m_type_combo->setCurrentIndex(0); } } else if (!m_ispool) { if (pvcount > 1) { m_type_combo->addItem(i18n("LVM2 Mirror")); m_type_combo->addItem(i18n("RAID 1 Mirror")); } if (pvcount > 2) { m_type_combo->addItem(i18n("RAID 4")); m_type_combo->addItem(i18n("RAID 5")); } if (pvcount > 4) { m_type_combo->addItem(i18n("RAID 6")); } } QLabel *const type_label = new QLabel(i18n("Volume type: ")); type_label->setBuddy(m_type_combo); if (m_snapshot) { m_type_combo->setCurrentIndex(0); m_type_combo->setEnabled(false); type_label->hide(); m_type_combo->hide(); } else if (m_extend) { m_type_combo->setEnabled(false); type_label->hide(); m_type_combo->hide(); } QHBoxLayout *const layout = new QHBoxLayout; layout->addWidget(type_label); layout->addWidget(m_type_combo); widget->setLayout(layout); return widget; } void LVCreateDialog::setMaxSize() { VolumeType type; const int stripes = m_stripe_count_spin->value(); const int mirrors = m_mirror_count_spin->value(); m_stripe_count_spin->setMaximum(getMaxStripes()); const long long max = getLargestVolume() / getVg()->getExtentSize(); const long long maxfs = getMaxFsSize() / getVg()->getExtentSize(); if (getExtendFs()) { if (max < maxfs) setSelectorMaxExtents(max); else setSelectorMaxExtents(maxfs); } else { setSelectorMaxExtents(max); } resetOkButton(); switch (m_type_combo->currentIndex()) { case 0: type = LINEAR; break; case 1: type = LVMMIRROR; break; case 2: type = RAID1; break; case 3: type = RAID4; break; case 4: type = RAID5; break; case 5: type = RAID6; break; default: type = LINEAR; break; } setInfoLabels(type, stripes, mirrors, getLargestVolume()); } void LVCreateDialog::resetOkButton() { const long long max = getLargestVolume() / getVg()->getExtentSize(); const long long selected = getSelectorExtents(); const long long rounded = roundExtentsToStripes(selected); if (!LvCreateDialogBase::isValid()) { if (rounded > max) { setWarningLabel("Selected size exceeds maximum size"); } else if (m_extend) { if (isLow()) setWarningLabel(i18n("Selected size less than existing size")); else clearWarningLabel(); } else { clearWarningLabel(); } enableButtonOk(false); } else { if (m_extend) { if ((rounded <= max) && (rounded >= m_lv->getExtents()) && (selected >= m_lv->getExtents())) { clearWarningLabel(); enableButtonOk(true); } else { setWarningLabel(i18n("Selected size exceeds maximum size")); enableButtonOk(false); } } else { if ((rounded <= max) && (rounded > 0)) { clearWarningLabel(); enableButtonOk(true); } else { setWarningLabel(i18n("Selected size exceeds maximum size")); enableButtonOk(false); } } } } void LVCreateDialog::enableTypeOptions(int index) { const int pv_count = m_pv_box->getAllNames().size(); if (index == 0) { // linear m_mirror_count_spin->setMinimum(1); m_mirror_count_spin->setValue(1); m_mirror_count_spin->setSpecialValueText(i18n("none")); m_mirror_count_spin->setEnabled(false); m_log_combo->setEnabled(false); m_log_combo->setCurrentIndex(1); m_stripe_count_spin->setRange(1, getMaxStripes()); if(!m_extend) m_stripe_count_spin->setValue(1); m_stripe_count_spin->setSuffix(""); m_stripe_count_spin->setSpecialValueText(i18n("none")); m_stripe_count_spin->setEnabled(true); m_stripe_widget->show(); m_mirror_widget->hide(); } else if (index == 1) { // LVM2 mirror if(m_extend) { m_mirror_count_spin->setEnabled(false); m_log_combo->setEnabled(false); } else { m_mirror_count_spin->setEnabled(true); m_log_combo->setEnabled(true); m_mirror_count_spin->setMinimum(2); m_mirror_count_spin->setValue(2); m_mirror_count_spin->setMaximum(pv_count); m_mirror_count_spin->setSpecialValueText(""); m_log_combo->setCurrentIndex(1); m_stripe_count_spin->setMinimum(1); m_stripe_count_spin->setValue(1); m_stripe_count_spin->setMaximum(getMaxStripes()); m_stripe_count_spin->setSuffix(""); m_stripe_count_spin->setSpecialValueText(i18n("none")); } m_stripe_count_spin->setEnabled(true); m_stripe_widget->show(); m_mirror_widget->show(); } else if (index == 2) { // RAID 1 if(m_extend) m_mirror_count_spin->setEnabled(false); else { m_mirror_count_spin->setMinimum(2); m_mirror_count_spin->setValue(2); m_mirror_count_spin->setMaximum(pv_count); m_mirror_count_spin->setSpecialValueText(""); m_mirror_count_spin->setEnabled(true); } m_log_combo->setEnabled(false); m_log_combo->setCurrentIndex(1); m_stripe_count_spin->setMinimum(1); m_stripe_count_spin->setValue(1); m_stripe_count_spin->setSuffix(""); m_stripe_count_spin->setSpecialValueText(i18n("none")); m_stripe_count_spin->setEnabled(false); m_stripe_size_combo->setEnabled(false); m_stripe_widget->hide(); m_mirror_widget->show(); } else if (index == 3) { // RAID 4 m_mirror_count_spin->setMinimum(1); m_mirror_count_spin->setValue(1); m_mirror_count_spin->setSpecialValueText(i18n("none")); m_mirror_count_spin->setEnabled(false); m_log_combo->setEnabled(false); m_log_combo->setCurrentIndex(1); if(m_extend) { m_stripe_count_spin->setEnabled(false); m_stripe_size_combo->setEnabled(false); } else{ m_stripe_count_spin->setMinimum(2); m_stripe_count_spin->setValue(2); m_stripe_count_spin->setMaximum(getMaxStripes() - 1); m_stripe_count_spin->setSpecialValueText(""); m_stripe_count_spin->setSuffix(i18n(" + 1 parity")); m_stripe_count_spin->setEnabled(true); } m_stripe_widget->show(); m_mirror_widget->hide(); } else if (index == 4) { // RAID 5 m_mirror_count_spin->setMinimum(1); m_mirror_count_spin->setValue(1); m_mirror_count_spin->setSpecialValueText(i18n("none")); m_mirror_count_spin->setEnabled(false); m_log_combo->setEnabled(false); m_log_combo->setCurrentIndex(1); if(m_extend) { m_stripe_count_spin->setEnabled(false); m_stripe_size_combo->setEnabled(false); } else { m_stripe_count_spin->setMinimum(2); m_stripe_count_spin->setValue(2); m_stripe_count_spin->setMaximum(getMaxStripes() - 1); m_stripe_count_spin->setSpecialValueText(""); m_stripe_count_spin->setSuffix(i18n(" + 1 parity")); m_stripe_count_spin->setEnabled(true); } m_stripe_widget->show(); m_mirror_widget->hide(); } else if (index == 5) { // RAID 6 m_mirror_count_spin->setMinimum(1); m_mirror_count_spin->setValue(1); m_mirror_count_spin->setSpecialValueText(i18n("none")); m_mirror_count_spin->setEnabled(false); m_log_combo->setEnabled(false); m_log_combo->setCurrentIndex(1); if(m_extend) { m_stripe_count_spin->setEnabled(false); m_stripe_size_combo->setEnabled(false); } else { m_stripe_count_spin->setMinimum(3); m_stripe_count_spin->setValue(3); m_stripe_count_spin->setMaximum(getMaxStripes() - 2); m_stripe_count_spin->setSpecialValueText(""); m_stripe_count_spin->setSuffix(i18n(" + 2 parity")); m_stripe_count_spin->setEnabled(true); } m_stripe_widget->show(); m_mirror_widget->hide(); } } void LVCreateDialog::enableStripeCombo(int value) { if (m_extend && m_type_combo->currentIndex() > 2) { m_stripe_size_combo->setEnabled(false); } else { if (value > 1 && m_stripe_size_combo->count() > 0) m_stripe_size_combo->setEnabled(true); else m_stripe_size_combo->setEnabled(false); } } void LVCreateDialog::enableMonitoring(int index) { if (index == 1 || index == 2) { // lvm mirror or raid mirror setMonitor(true); // whether a snap or not enableMonitor(true); enableSkipSync(true); } else if (index > 2 || m_snapshot){ // raid stripe set setMonitor(true); // and all other snaps enableMonitor(true); setSkipSync(false); enableSkipSync(false); } else { // linear setMonitor(false); enableMonitor(false); setSkipSync(false); enableSkipSync(false); } } /* largest volume that can be created given the pvs, striping and mirrors selected. This includes the size of the already existing volume if we are extending a volume */ long long LVCreateDialog::getLargestVolume() { const int type = m_type_combo->currentIndex(); const int stripe_count = m_stripe_count_spin->value(); const long long extent_size = getVg()->getExtentSize(); const AllocationPolicy policy = m_pv_box->getEffectivePolicy(); QList stripe_pv_bytes; if (!getPvsByPolicy(stripe_pv_bytes)) return 0; if (!reservePoolMetadata(stripe_pv_bytes)) return 0; long long largest = stripe_pv_bytes[0]; if (!m_extend) { if (type == 2) largest = largest - extent_size; // RAID 1 uses one extent per mirror for metadata else if (type == 3 || type == 4 || type == 5) largest = (largest - extent_size) * stripe_count; // RAID 4/5/6 use one per stripe else largest = largest * stripe_count; } else { LogVol *effective_lv = m_lv; if (effective_lv->isThinPool()) { for (auto data : effective_lv->getChildren()) { if (data->isThinPoolData()) { effective_lv = data; break; } } } if (effective_lv->isMirror() && policy == CONTIGUOUS) { const LvList legs = effective_lv->getChildren(); // not grandchildren because we can't extend while under conversion QList leg_max; for (int x = legs.size() - 1; x >= 0; x--) { if (legs[x]->isMirrorLeg()) { const QStringList pv_names = legs[x]->getPvNames(legs[x]->getSegmentCount() - 1); QList stripe_max; for (int y = pv_names.size() - 1; y >= 0; y--) stripe_max.append(getVg()->getPvByName(pv_names[y])->getContiguous(effective_lv)); qSort(stripe_max); if (stripe_max.size() < stripe_count) return effective_lv->getSize(); while (stripe_max.size() > stripe_count) stripe_max.removeFirst(); leg_max.append(stripe_max[0] * stripe_count); } } qSort(leg_max); largest = leg_max[0] + effective_lv->getSize(); } else if (effective_lv->isRaid() && policy == CONTIGUOUS) { const LvList images = effective_lv->getChildren(); QList image_max; for (auto img : images) { if (img->isRaidImage()) { const QStringList pv_names = img->getPvNames(img->getSegmentCount() - 1); long long stripe_max = getVg()->getPvByName(pv_names[0])->getContiguous(effective_lv); image_max.append(stripe_max); } } qSort(image_max); largest = (image_max[0] * stripe_count) + effective_lv->getSize(); } else if (policy == CONTIGUOUS) { const QStringList pv_names = effective_lv->getPvNames(effective_lv->getSegmentCount() - 1); QList stripe_max; for (int y = pv_names.size() - 1; y >= 0; y--) stripe_max.append(getVg()->getPvByName(pv_names[y])->getContiguous(effective_lv)); qSort(stripe_max); if (stripe_max.size() < stripe_count) return effective_lv->getSize(); while (stripe_max.size() > stripe_count) stripe_max.removeFirst(); largest = (stripe_max[0] * stripe_count) + effective_lv->getSize(); } else { largest = (largest * stripe_count) + effective_lv->getSize(); } } if (largest < 0) largest = 0; return largest; } /* Here we create a stringlist of arguments based on all the options that the user chose in the dialog. */ QStringList LVCreateDialog::args() { QString program_to_run; QStringList args; const QVariant stripe_size = m_stripe_size_combo->itemData(m_stripe_size_combo->currentIndex(), Qt::UserRole); const int stripes = m_stripe_count_spin->value(); const int type = m_type_combo->currentIndex(); const int mirrors = m_mirror_count_spin->value(); long long extents = getSelectorExtents(); if (!m_extend) { if (!getTag().isEmpty()) args << "--addtag" << getTag(); if (getPersistent()) { args << "--persistent" << "y"; args << "--major" << getMajor(); args << "--minor" << getMinor(); } if (!m_ispool) { if (getReadOnly()) args << "--permission" << "r" ; else args << "--permission" << "rw" ; } if (type == 1) args << "--type" << "mirror" ; else if (type == 2) args << "--type" << "raid1" ; else if (type == 3) args << "--type" << "raid4" ; else if (type == 4) args << "--type" << "raid5" ; else if (type == 5) args << "--type" << "raid6" ; if (!m_snapshot && !m_extend) { if (getZero()) args << "--zero" << "y"; else args << "--zero" << "n"; if (mirrors > 1) { args << "--mirrors" << QString("%1").arg(mirrors - 1); if (getSkipSync()) args << "--nosync"; if (type == 1) { // traditional mirror if (m_log_combo->currentIndex() == 0) args << "--mirrorlog" << "mirrored"; else if (m_log_combo->currentIndex() == 1) args << "--mirrorlog" << "disk"; else args << "--mirrorlog" << "core"; } } } } if (!getUdev()) args << "--noudevsync"; if (stripes > 1) { args << "--stripes" << QString("%1").arg(stripes); args << "--stripesize" << QString("%1").arg(stripe_size.toLongLong()); } else if (m_extend && (type == 0 || type == 1)) { args << "--stripes" << QString("%1").arg(1); } if (type > 0 && !m_extend) { args << "--monitor"; if (getMonitor()) args << "y"; else args << "n"; } if (m_extend || (m_pv_box->getPolicy() <= ANYWHERE)) args << "--alloc" << policyToString(m_pv_box->getEffectivePolicy()); // don't pass INHERIT_* else args << "--alloc" << "inherit"; if (m_extend) extents -= m_lv->getExtents(); extents = roundExtentsToStripes(extents); args << "--extents" << QString("+%1").arg(extents); if (m_ispool && !m_extend) { // create a thin pool program_to_run = "lvcreate"; args << "--chunksize" << QString("%1k").arg(getChunkSize()/1024); args << "--thinpool" << getName(); args << getVg()->getName(); } else if (!m_extend && !m_snapshot) { // create a standard volume program_to_run = "lvcreate"; if (!getName().isEmpty()) args << "--name" << getName(); args << getVg()->getName(); } else if (m_snapshot) { // create a snapshot program_to_run = "lvcreate"; args << "--snapshot"; if (!getName().isEmpty()) args << "--name" << getName(); args << m_lv->getFullName(); } else { // extend the current volume program_to_run = "lvextend"; args << m_lv->getFullName(); } args << m_pv_box->getNames(); args.prepend(program_to_run); return args; } // make the number of extents divivsible by the stripe X mirror count then round up long long LVCreateDialog::roundExtentsToStripes(long long extents) { const int stripes = m_stripe_count_spin->value(); const int mirrors = m_mirror_count_spin->value(); long long max_extents; if (m_extend) max_extents = (getLargestVolume() - m_lv->getSize()) / getVg()->getExtentSize(); else max_extents = getLargestVolume() / getVg()->getExtentSize(); // The next part should only need to reference stripes, not the mirror count // but a bug in lvm requires it. Remove this when fixed. if (stripes > 1) { if (extents % (stripes * mirrors)) { extents = extents / (stripes * mirrors); extents = extents * (stripes * mirrors); if (extents + (stripes * mirrors) <= max_extents) { extents += (stripes * mirrors); } } } return extents; } // This function checks for problems that would make showing this dialog pointless // returns true if there are problems and is used to set m_bailout. bool LVCreateDialog::hasInitialErrors() { const VolGroup *const vg = getVg(); if (vg->getAllocatableExtents() <= 0 || vg->isPartial()) { if (vg->isPartial()) if (m_extend) KMessageBox::sorry(this, i18n("Volumes can not be extended while physical volumes are missing")); else KMessageBox::sorry(this, i18n("Volumes can not be created while physical volumes are missing")); else if (vg->getFreeExtents()) KMessageBox::sorry(this, i18n("All free physical volume extents in this group" " are locked against allocation")); else KMessageBox::sorry(this, i18n("There are no free extents in this volume group")); return true; } if (m_extend) { const QString warning1 = i18n("If this volume has a filesystem or data, it will need to be extended later " "by an appropriate tool. \n \n" "Currently, only the ext2, ext3, ext4, xfs, jfs, ntfs and reiserfs file systems are " "supported for extension. "); const QString warning2 = i18n("This filesystem seems to be as large as it can get, it will not be extended with the volume"); const QString warning3 = i18n("ntfs cannot be extended while mounted. The filesystem will need to be " "extended later or unmounted before the volume is extended."); if (m_lv->isRaid() || m_lv->isLvmMirror()){ QList pvs = vg->getPhysicalVolumes(); for (int x = pvs.size() - 1; x >= 0; x--) { if (pvs[x]->getRemaining() < 1 || !pvs[x]->isAllocatable()) pvs.removeAt(x); } if (m_lv->getMirrorCount() > pvs.size() || (m_lv->isRaid() && m_lv->getSegmentStripes(0) > pvs.size())) { KMessageBox::sorry(this, i18n("Insufficient allocatable physical volumes to extend this volume")); return true; } } if (m_lv->isCowOrigin()) { if (m_lv->isOpen()) { KMessageBox::sorry(this, i18n("Snapshot origins cannot be extended while active")); return true; } for (const auto snap : m_lv->getSnapshots()) { if (snap->isOpen()) { KMessageBox::sorry(this, i18n("Volumes cannot be extended with open or mounted snapshots")); return true; } } } if (!m_ispool && !m_lv->isCowSnap()) { const long long maxfs = getMaxFsSize() / m_lv->getVg()->getExtentSize(); const long long current = m_lv->getExtents(); if ((m_lv->getFilesystem() == "ntfs") && m_lv->isMounted()) { if (KMessageBox::warningContinueCancel(nullptr, warning3, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return true; } } else if (!fs_can_extend(m_lv->getFilesystem(), m_lv->isMounted())) { if (KMessageBox::warningContinueCancel(nullptr, warning1, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return true; } } else if (current >= maxfs) { if (KMessageBox::warningContinueCancel(nullptr, warning2, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return true; } } } } return false; } void LVCreateDialog::commit() { QStringList lvchange_args; hide(); if (!m_extend) { ProcessProgress create_lv(args()); return; } else { const QString mapper_path = m_lv->getMapperPath(); const QString fs = m_lv->getFilesystem(); if (m_lv->isCowOrigin()) { lvchange_args << "lvchange" << "-an" << mapper_path; ProcessProgress deactivate_lv(lvchange_args); if (deactivate_lv.exitCode()) { KMessageBox::error(0, i18n("Volume deactivation failed, volume not extended")); return; } ProcessProgress extend_origin(args()); if (extend_origin.exitCode()) { KMessageBox::error(0, i18n("Volume extension failed")); return; } lvchange_args.clear(); lvchange_args << "lvchange" << "-ay" << mapper_path; ProcessProgress activate_lv(lvchange_args); if (activate_lv.exitCode()) { if (getExtendFs()) KMessageBox::error(0, i18n("Volume activation failed, filesystem not extended")); else KMessageBox::error(0, i18n("Volume activation failed")); } else if (getExtendFs()) { if (!fs_extend(m_lv->getMapperPath(), fs, m_lv->getMountPoints(), true)) { KMessageBox::error(nullptr, i18n("Filesystem extention failed")); } } return; } else { ProcessProgress extend_lv(args()); if (!extend_lv.exitCode() && !m_lv->isCowSnap() && getExtendFs()) { if(!fs_extend(mapper_path, fs, m_lv->getMountPoints(), true)) KMessageBox::error(nullptr, i18n("Filesystem extention failed")); } return; } } } int LVCreateDialog::getMaxStripes() { int stripes = m_pv_box->getAllNames().size(); AllocationPolicy policy = m_pv_box->getEffectivePolicy(); if (m_extend && (policy == CONTIGUOUS)){ if (m_lv->isLvmMirror()){ LvList legs = m_lv->getChildren(); int next_stripes = 0; for (int x = legs.size() - 1; x >= 0; --x){ if (legs[x]->isMirrorLeg()) { next_stripes = legs[x]->getSegmentStripes(legs[x]->getSegmentCount() - 1); if (next_stripes < stripes) stripes = next_stripes; } } } } if (stripes < 1) stripes = 1; return stripes; } // This function tries to extend the pvs of the last segment, if they have been selected, // before applying the usual methods of adding segments. lvextend with "normal" allocation // seems to work this way. void LVCreateDialog::extendLastSegment(QList &committed, QList &available) { LvList legs; QStringList selected_names(m_pv_box->getNames()); LogVol *lv = m_lv; if (lv->isThinPool()) { legs = lv->getChildren(); for (int x = legs.size() - 1; x >= 0; x--) { if (legs[x]->isThinPoolData()) { lv = legs[x]; break; } } } legs.clear(); if (lv->isMirror()) { legs = lv->getChildren(); // not grandchildren because we can't extend while under conversion for (int x = legs.size() - 1; x >= 0; x--) { if (!legs[x]->isMirrorLeg()) legs.removeAt(x); } } else { legs.append(lv); } int commit_count = 0; for (int x = legs.size() - 1; x >= 0; x--) { const QStringList pv_names = legs[x]->getPvNames(legs[x]->getSegmentCount() - 1); for (int y = selected_names.size() - 1; y >= 0; y--) { for (int z = pv_names.size() - 1; z >= 0; z--) { if ((selected_names[y] == pv_names[z]) && commit_count < committed.size()) { committed[commit_count] += available.takeAt(y); selected_names.removeAt(y); commit_count++; break; } } } } } int LVCreateDialog::getLogCount() { const int type = m_type_combo->currentIndex(); int count = 0; if (type == 1 && !m_extend) { if (m_log_combo->currentIndex() == 0) count = 2; else if (m_log_combo->currentIndex() == 1) count = 1; else count = 0; } else { count = 0; } return count; } int LVCreateDialog::getNeededStripes() { int total; const int type = m_type_combo->currentIndex(); const int stripes = m_stripe_count_spin->value(); const int mirrors = m_mirror_count_spin->value(); if (type == 1) // LVM2 mirror total = stripes * mirrors; else if (type == 2) // RAID 1 mirror total = mirrors; else total = stripes; if (type == 3) // RAID 4 total += 1; else if (type == 4) // RAID 5 total += 1; else if (type == 5) // RAID 6 total += 2; return total; } // Remove pvs that are selected but cannot be used because of policy // and set aside some space on the usable ones for a mirror log if // one is needed. bool LVCreateDialog::getPvsByPolicy(QList &usableBytes) { const AllocationPolicy policy = m_pv_box->getEffectivePolicy(); const long long extent_size = getVg()->getExtentSize(); const int log_count = getLogCount(); const bool separate_logs = LvmConfig::getMirrorLogsRequireSeparatePvs(); const bool separate_meta = LvmConfig::getThinPoolMetadataRequireSeparatePvs(); long long reserved = 0; while (reserved < 0x100000) // reserve 1 Meg for each mirror log reserved += extent_size; for (int x = 0; x < getNeededStripes(); ++x) usableBytes.append(0); QList pv_bytes = m_pv_box->getRemainingSpaceList(); qSort(pv_bytes); if (!m_extend) { int extra_pvs = log_count; if (m_ispool && (separate_meta || policy == CONTIGUOUS)) { ++extra_pvs; usableBytes.append(0); } // Specifying CONTIGUOUS seems to trigger the need for separate pvs too if ((policy == CONTIGUOUS) || ((log_count > 0) && separate_logs)) { if (policy == CONTIGUOUS) { while (pv_bytes.size() > getNeededStripes() + extra_pvs) pv_bytes.removeFirst(); } for (int x = 0; x < log_count; ++x) { if (pv_bytes.size()) pv_bytes.removeFirst(); else return false; } } else { if (pv_bytes.size() >= log_count) { for (int x = pv_bytes.size() - 1; x >= (pv_bytes.size() - log_count); --x) { pv_bytes[x] -= reserved; if (pv_bytes[x] < 0) return false; } } else { return false; } } } else { extendLastSegment(usableBytes, pv_bytes); } while (pv_bytes.size()) { qSort(usableBytes); usableBytes[0] += pv_bytes.takeLast(); } qSort(usableBytes); return true; } // The next function sets aside the space needed for thin pool metadata. // The data doesn't grow upon extending of the pool. bool LVCreateDialog::reservePoolMetadata(QList &usableBytes) { if (m_ispool && !m_extend) { m_ispool = false; long long meta = 64 * (getLargestVolume() / getChunkSize(getLargestVolume())); m_ispool = true; const long long ext = getVg()->getExtentSize(); meta = ((meta + ext - 1) / ext) * ext; if (meta < 0x200000) // 2 MiB meta = 0x200000; else if (meta > 0x400000000) // 16 GiB meta = 0x400000000; const AllocationPolicy policy = m_pv_box->getEffectivePolicy(); const bool separate_pvs = LvmConfig::getThinPoolMetadataRequireSeparatePvs(); if(policy == CONTIGUOUS || separate_pvs) { if (usableBytes.size() > getNeededStripes()) { int x = usableBytes.size() - (1 + getNeededStripes()); usableBytes[x] -= meta; if (usableBytes[x] < 0) return false; else usableBytes.removeAt(x); // not usable for main pool volume so discard } else { return false; } } else { usableBytes.last() -= meta; if (usableBytes.last() < 0) return false; } qSort(usableBytes); } return true; } QList> LVCreateDialog::getPvSpaceList() { QList> list; QList pvs(getVg()->getPhysicalVolumes()); for (auto pv : pvs) { if (pv->getRemaining() > 0 && pv->isAllocatable() && !pv->isMissing()) list << QSharedPointer(new PvSpace(pv, pv->getRemaining(), pv->getContiguous(m_lv))); } return list; } kvpm-0.9.10/kvpm/lvchange.h0000644000175000017500000000353312770324126015727 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVCHANGE_H #define LVCHANGE_H #include #include "kvpmdialog.h" class QCheckBox; class QComboBox; class QGroupBox; class QLineEdit; class QRadioButton; class LogVol; class PolicyComboBox; class LVChangeDialog : public KvpmDialog { Q_OBJECT LogVol *m_lv; PolicyComboBox *m_policy_combo; QCheckBox *m_available_check, // Make the volume available *m_ro_check, // make the volume read only *m_refresh_check, // refresh the metadata *m_udevsync_check, // sync with udev *m_persistent_check; // Set persistent kernel device numbers QRadioButton *m_poll_button, *m_nopoll_button, *m_monitor_button, // dmeventd monitoring *m_nomonitor_button, *m_ignore_button; // ignore dmeventd monitoring QGroupBox *m_devnum_box, *m_dmeventd_box, *m_polling_box; QLineEdit *m_minor_edit, // User entered device minor number *m_major_edit, // User entered device major number *m_tag_edit; // new tag QComboBox *m_deltag_combo; QWidget *buildGeneralTab(); QWidget *buildAdvancedTab(); QStringList arguments(); public: explicit LVChangeDialog(LogVol *const volume, QWidget *parent = nullptr); private slots: void commit(); void resetOkButton(); void refreshAndAvailableCheck(); }; #endif kvpm-0.9.10/kvpm/resync.cpp0000644000175000017500000000317612770324126016001 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "resync.h" #include #include #include #include "logvol.h" #include "processprogress.h" bool resync(LogVol *const lv) { if (lv->isOpen() || lv->isMounted()) { const QString error = i18n("Volume %1 is in use or mounted and cannot " "be deactivated for re-synchronization" , lv->getName()); KMessageBox::sorry(nullptr, error); return false; } const QString warning = i18n("Really re-synchronize %1? " "That could take a long time.", lv->getName()); if (KMessageBox::warningYesNo(nullptr, warning, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { QStringList args; args << "lvchange" << "--yes" << "--resync" << lv->getFullName(); ProcessProgress resync(args); return true; } else { return false; } } kvpm-0.9.10/kvpm/vgsplit.cpp0000644000175000017500000005045712770324126016172 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011, 2012, 2013, 2014 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgsplit.h" #include "masterlist.h" #include "misc.h" #include "physvol.h" #include "processprogress.h" #include "volgroup.h" #include #include #include #include #include #include #include #include #include #include #include VGSplitDialog::VGSplitDialog(VolGroup *volumeGroup, QWidget *parent) : KvpmDialog(parent), m_vg(volumeGroup) { QList pv_list = m_vg->getPhysicalVolumes(); setCaption(i18n("Split Volume Group")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); QLabel *const name_label = new QLabel(i18n("Volume group to split: %1", m_vg->getName())); name_label->setAlignment(Qt::AlignCenter); layout->addWidget(name_label); layout->addSpacing(10); m_new_vg_name = new QLineEdit(); QRegExp rx("[0-9a-zA-Z_\\.][-0-9a-zA-Z_\\.]*"); m_validator = new QRegExpValidator(rx, m_new_vg_name); m_new_vg_name->setValidator(m_validator); QLabel *const new_name_label = new QLabel(i18n("New Volume Group Name")); new_name_label->setBuddy(m_new_vg_name); QHBoxLayout *const new_name_layout = new QHBoxLayout(); new_name_layout->addStretch(); new_name_layout->addWidget(new_name_label); new_name_layout->addWidget(m_new_vg_name); new_name_layout->addStretch(); layout->addLayout(new_name_layout); connect(m_new_vg_name, SIGNAL(textEdited(QString)), this, SLOT(validateOK())); QTabWidget *const tw = new QTabWidget(); layout->addWidget(tw); QStringList mobile_lv_names, fixed_lv_names, mobile_pv_names, fixed_pv_names; volumeMobility(mobile_lv_names, fixed_lv_names, mobile_pv_names, fixed_pv_names); tw->addTab(buildLvLists(mobile_lv_names, fixed_lv_names), i18n("Logical volume view")); tw->addTab(buildPvLists(mobile_pv_names, fixed_pv_names), i18n("Physical volume view")); enableLvArrows(); enablePvArrows(); validateOK(); setMinimumWidth(400); if (m_vg->getPhysicalVolumes().size() < 2) { KMessageBox::sorry(nullptr, i18n("A volume group must have at least two physical volumes to split group")); preventExec(); } } void VGSplitDialog::validateOK() { QString name = m_new_vg_name->text(); int pos = 0; bool original_vg = false; bool new_vg = false; if (m_validator->validate(name, pos) == QValidator::Acceptable && name != "." && name != "..") { if (m_left_pv_list->count()) // Must have at least one pv in old group and one in new group original_vg = true; if (m_right_pv_list->count()) new_vg = true; if (new_vg && original_vg) enableButtonOk(true); else enableButtonOk(false); } else { enableButtonOk(false); } } void VGSplitDialog::commit() { deactivate(); QStringList args = QStringList() << "vgsplit" << m_vg->getName() << m_new_vg_name->text(); for (int x = m_right_pv_list->count() - 1; x >= 0; x--) args << m_right_pv_list->item(x)->data(Qt::DisplayRole).toString(); ProcessProgress vgsplit(args); } void VGSplitDialog::deactivate() { QStringList moving_lvs; const QByteArray vg_name = m_vg->getName().toLocal8Bit(); lvm_t lvm = MasterList::getLvm(); vg_t vg_dm; dm_list *lv_dm_list; lvm_lv_list *lv_list; QList lvs_to_deactivate; for (int x = m_right_lv_list->count() - 1; x >= 0; x--) moving_lvs << m_right_lv_list->item(x)->data(Qt::DisplayRole).toString(); if ((vg_dm = lvm_vg_open(lvm, vg_name.data(), "w", 0x0))) { for (int x = 0; x < moving_lvs.size(); x++) { if ((lv_dm_list = lvm_vg_list_lvs(vg_dm))) { dm_list_iterate_items(lv_list, lv_dm_list) { if (QString(lvm_lv_get_name(lv_list->lv)).trimmed() == moving_lvs[x]) lvs_to_deactivate.append(lv_list->lv); } } } for (int x = 0; x < lvs_to_deactivate.size(); x++) { if (lvm_lv_is_active(lvs_to_deactivate[x])) { if (lvm_lv_deactivate(lvs_to_deactivate[x])) KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); } } lvm_vg_close(vg_dm); return; } else { KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); return; } return; } QWidget *VGSplitDialog::buildLvLists(const QStringList mobileLvNames, const QStringList fixedLvNames) { QWidget *const lv_list = new QWidget(); QHBoxLayout *const layout = new QHBoxLayout; QVBoxLayout *const left_layout = new QVBoxLayout; QVBoxLayout *const center_layout = new QVBoxLayout; QVBoxLayout *const right_layout = new QVBoxLayout; m_lv_add = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-right")), i18n("Add")); m_lv_remove = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-left")), i18n("Remove")); m_left_lv_list = new QListWidget(); m_right_lv_list = new QListWidget(); m_left_lv_list->setSelectionMode(QAbstractItemView::ExtendedSelection); m_right_lv_list->setSelectionMode(QAbstractItemView::ExtendedSelection); m_left_lv_list->setSortingEnabled(true); m_right_lv_list->setSortingEnabled(true); QListWidgetItem *lv_item; for (int x = mobileLvNames.size() - 1; x >= 0; x--) { lv_item = new QListWidgetItem(mobileLvNames[x]); lv_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); m_left_lv_list->addItem(lv_item); } for (int x = fixedLvNames.size() - 1; x >= 0; x--) { lv_item = new QListWidgetItem(fixedLvNames[x]); lv_item->setFlags(Qt::NoItemFlags); m_left_lv_list->addItem(lv_item); } QLabel *temp_label = new QLabel(i18n("Original volume group")); temp_label->setAlignment(Qt::AlignCenter); left_layout->addWidget(temp_label); left_layout->addWidget(m_left_lv_list); layout->addLayout(left_layout); center_layout->addStretch(); center_layout->addWidget(m_lv_add); center_layout->addWidget(m_lv_remove); center_layout->addStretch(); layout->addLayout(center_layout); temp_label = new QLabel(i18n("New volume group")); temp_label->setAlignment(Qt::AlignCenter); right_layout->addWidget(temp_label); right_layout->addWidget(m_right_lv_list); layout->addLayout(right_layout); lv_list->setLayout(layout); connect(m_left_lv_list, SIGNAL(itemSelectionChanged()), this, SLOT(enableLvArrows())); connect(m_right_lv_list, SIGNAL(itemSelectionChanged()), this, SLOT(enableLvArrows())); connect(m_lv_add, SIGNAL(clicked()), this, SLOT(addLvList())); connect(m_lv_remove, SIGNAL(clicked()), this, SLOT(removeLvList())); return lv_list; } QWidget *VGSplitDialog::buildPvLists(const QStringList mobilePvNames, const QStringList fixedPvNames) { QWidget *const pv_list = new QWidget(); QHBoxLayout *const layout = new QHBoxLayout; QVBoxLayout *const left_layout = new QVBoxLayout; QVBoxLayout *const center_layout = new QVBoxLayout; QVBoxLayout *const right_layout = new QVBoxLayout; m_pv_add = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-right")), "Add"); m_pv_remove = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-left")), "Remove"); QListWidgetItem *pv_item; QStringList open_pv_names; QStringList closed_pv_names; m_left_pv_list = new QListWidget(); m_right_pv_list = new QListWidget(); m_left_pv_list->setSelectionMode(QAbstractItemView::ExtendedSelection); m_right_pv_list->setSelectionMode(QAbstractItemView::ExtendedSelection); m_left_pv_list->setSortingEnabled(true); m_right_pv_list->setSortingEnabled(true); for (int x = mobilePvNames.size() - 1; x >= 0; x--) { pv_item = new QListWidgetItem(mobilePvNames[x]); pv_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); m_left_pv_list->addItem(pv_item); } for (int x = fixedPvNames.size() - 1; x >= 0; x--) { pv_item = new QListWidgetItem(fixedPvNames[x]); pv_item->setFlags(Qt::NoItemFlags); m_left_pv_list->addItem(pv_item); } QLabel *temp_label = new QLabel(i18n("Original volume group")); temp_label->setAlignment(Qt::AlignCenter); left_layout->addWidget(temp_label); left_layout->addWidget(m_left_pv_list); layout->addLayout(left_layout); center_layout->addStretch(); center_layout->addWidget(m_pv_add); center_layout->addWidget(m_pv_remove); center_layout->addStretch(); layout->addLayout(center_layout); temp_label = new QLabel(i18n("New volume group")); temp_label->setAlignment(Qt::AlignCenter); right_layout->addWidget(temp_label); right_layout->addWidget(m_right_pv_list); layout->addLayout(right_layout); pv_list->setLayout(layout); connect(m_left_pv_list, SIGNAL(itemSelectionChanged()), this, SLOT(enablePvArrows())); connect(m_right_pv_list, SIGNAL(itemSelectionChanged()), this, SLOT(enablePvArrows())); connect(m_pv_add, SIGNAL(clicked()), this, SLOT(addPvList())); connect(m_pv_remove, SIGNAL(clicked()), this, SLOT(removePvList())); return pv_list; } void VGSplitDialog::enableLvArrows() { if (m_left_lv_list->selectedItems().size()) m_lv_add->setEnabled(true); else m_lv_add->setEnabled(false); if (m_right_lv_list->selectedItems().size()) m_lv_remove->setEnabled(true); else m_lv_remove->setEnabled(false); } void VGSplitDialog::enablePvArrows() { if (m_left_pv_list->selectedItems().size()) m_pv_add->setEnabled(true); else m_pv_add->setEnabled(false); if (m_right_pv_list->selectedItems().size()) m_pv_remove->setEnabled(true); else m_pv_remove->setEnabled(false); } void VGSplitDialog::addLvList() { moveNames(true, m_left_lv_list, m_right_lv_list, m_left_pv_list, m_right_pv_list); } void VGSplitDialog::removeLvList() { moveNames(true, m_right_lv_list, m_left_lv_list, m_right_pv_list, m_left_pv_list); } void VGSplitDialog::addPvList() { moveNames(false, m_left_lv_list, m_right_lv_list, m_left_pv_list, m_right_pv_list); } void VGSplitDialog::removePvList() { moveNames(false, m_right_lv_list, m_left_lv_list, m_right_pv_list, m_left_pv_list); } void VGSplitDialog::moveNames(const bool isLvMove, QListWidget *const lvSource, QListWidget *const lvTarget, QListWidget *const pvSource, QListWidget *const pvTarget) { QList selected_items; QList moving_items; QStringList moving_pv_names, moving_lv_names; QStringList pv_names, lv_names; QString name; if (isLvMove) { selected_items = lvSource->selectedItems(); for (int x = selected_items.size() - 1; x >= 0; x--) { name = lvSource->item(lvSource->row(selected_items[x]))->data(Qt::DisplayRole).toString(); movesWithVolume(true, name, pv_names, lv_names); moving_pv_names.append(pv_names); moving_lv_names.append(lv_names); } moving_pv_names.removeDuplicates(); moving_lv_names.removeDuplicates(); } else { selected_items = pvSource->selectedItems(); for (int x = selected_items.size() - 1; x >= 0; x--) { name = pvSource->item(pvSource->row(selected_items[x]))->data(Qt::DisplayRole).toString(); movesWithVolume(false, name, pv_names, lv_names); moving_pv_names.append(pv_names); moving_lv_names.append(lv_names); } moving_pv_names.removeDuplicates(); moving_lv_names.removeDuplicates(); } for (int x = moving_lv_names.size() - 1; x >= 0; x--) { moving_items = lvSource->findItems(moving_lv_names[x], Qt::MatchExactly); for (int y = moving_items.size() - 1; y >= 0; y--) { lvSource->takeItem(lvSource->row(moving_items[y])); lvTarget->addItem(moving_items[y]); } } for (int x = moving_pv_names.size() - 1; x >= 0; x--) { moving_items = pvSource->findItems(moving_pv_names[x], Qt::MatchExactly); for (int y = moving_items.size() - 1; y >= 0; y--) { pvSource->takeItem(pvSource->row(moving_items[y])); pvTarget->addItem(moving_items[y]); } } validateOK(); } void VGSplitDialog::volumeMobility(QStringList &mobileLvNames, QStringList &fixedLvNames, QStringList &mobilePvNames, QStringList &fixedPvNames) { const LvList lvs = getFullLvList(); const QList pvs = m_vg->getPhysicalVolumes(); bool growing = true; int list_size = 0; mobileLvNames.clear(); mobilePvNames.clear(); fixedLvNames.clear(); fixedPvNames.clear(); pvState(fixedPvNames, mobilePvNames); QStringList lv_pv_names; while (growing) { growing = false; for (int x = lvs.size() - 1; x >= 0 ; x--) { lv_pv_names = getPvs(lvs[x]); for (int y = lv_pv_names.size() - 1; y >= 0; y--) { for (int z = fixedPvNames.size() - 1; z >= 0; z--) { if (fixedPvNames[z] == lv_pv_names[y]) { fixedPvNames.append(lv_pv_names); fixedLvNames.append(lvs[x]->getName()); break; } } } } fixedLvNames.removeDuplicates(); fixedPvNames.removeDuplicates(); if (fixedLvNames.size() + fixedPvNames.size() > list_size) { growing = true; list_size = fixedLvNames.size() + fixedPvNames.size(); } } fixedLvNames.sort(); fixedPvNames.sort(); mobileLvNames.clear(); mobilePvNames.clear(); for (int x = lvs.size() - 1; x >= 0; x--) mobileLvNames.append(lvs[x]->getName()); for (int x = mobileLvNames.size() - 1; x >= 0; x--) { for (int y = fixedLvNames.size() - 1; y >= 0; y--) { if (mobileLvNames[x] == fixedLvNames[y]) { mobileLvNames.removeAt(x); break; } } } for (int x = pvs.size() - 1; x >= 0; x--) mobilePvNames.append(pvs[x]->getMapperName()); for (int x = mobilePvNames.size() - 1; x >= 0; x--) { for (int y = fixedPvNames.size() - 1; y >= 0; y--) { if (mobilePvNames[x] == fixedPvNames[y]) { mobilePvNames.removeAt(x); break; } } } mobileLvNames.removeDuplicates(); mobilePvNames.removeDuplicates(); mobileLvNames.sort(); mobilePvNames.sort(); } void VGSplitDialog::pvState(QStringList &open, QStringList &closed) { const LvList lvs = getFullLvList(); for (int x = lvs.size() - 1; x >= 0; x--) { if (lvs[x]->isOpen()) { open.append(lvs[x]->getPvNamesAllFlat()); if (lvs[x]->isSnapContainer()) { const LvList snaps(lvs[x]->getSnapshots()); for (int y = snaps.size() - 1; y >= 0; y--) { if (snaps[y]->isOpen()) { open.append(lvs[x]->getPvNamesAllFlat()); // if any snap is open the whole container is open break; } } } if (lvs[x]->isThinVolume()) { LogVol *const pool = m_vg->getLvByName(lvs[x]->getPoolName()); const LvList thinvols(pool->getThinVolumes()); for (int y = thinvols.size() - 1; y >= 0; y--) { if (thinvols[y]->isOpen()) { // if any thin volume is open the whole pool is open open.append(m_vg->getLvByName(lvs[x]->getPoolName())->getPvNamesAllFlat()); break; } } } } } open.removeDuplicates(); const QList pvs = m_vg->getPhysicalVolumes(); QStringList all_pv_names; for (int x = pvs.size() - 1; x >= 0; x--) all_pv_names.append(pvs[x]->getMapperName()); for (int x = all_pv_names.size() - 1; x >= 0; x--) { for (int y = open.size() - 1; y >= 0; y--) { if (all_pv_names[x] == open[y]) { all_pv_names.removeAt(x); break; } } } closed = all_pv_names; } void VGSplitDialog::movesWithVolume(const bool isLV, const QString name, QStringList &movingPvNames, QStringList &movingLvNames) { const LvList lvs = getFullLvList(); const QList pvs = m_vg->getPhysicalVolumes(); LogVol *temp; bool growing = true; bool moving = true; int moving_lv_count; int moving_pv_count; movingPvNames.clear(); movingLvNames.clear(); if (isLV) { moving_lv_count = 1; moving_pv_count = 0; temp = m_vg->getLvByName(name); if (temp->isCowOrigin() && (temp->getParent() != nullptr)) movingPvNames = temp->getParent()->getPvNamesAllFlat(); else if (temp->isThinVolume()) movingPvNames = m_vg->getLvByName(temp->getPoolName())->getPvNamesAllFlat(); else movingPvNames = temp->getPvNamesAllFlat(); } else { moving_lv_count = 0; moving_pv_count = 1; movingPvNames.append(name); } QStringList lv_pv_names; while (growing) { for (int x = lvs.size() - 1; x >= 0 ; x--) { lv_pv_names = getPvs(lvs[x]); moving = false; for (int y = lv_pv_names.size() - 1; y >= 0; y--) { for (int z = movingPvNames.size() - 1; z >= 0; z--) { if (movingPvNames[z] == lv_pv_names[y]) { moving = true; break; } } } if (moving) movingLvNames.append(lvs[x]->getName()); } for (int x = movingLvNames.size() - 1; x >= 0; x--) { temp = m_vg->getLvByName(movingLvNames[x]); if (temp->isCowOrigin() && (temp->getParent() != nullptr)) movingPvNames.append(temp->getParent()->getPvNamesAllFlat()); else movingPvNames.append(temp->getPvNamesAllFlat()); } movingLvNames.removeDuplicates(); movingPvNames.removeDuplicates(); if ((movingLvNames.size() > moving_lv_count) || (movingPvNames.size() > moving_pv_count)) growing = true; else growing = false; moving_lv_count = movingLvNames.size(); moving_pv_count = movingPvNames.size(); } } LvList VGSplitDialog::getFullLvList() { LvList lvs = m_vg->getLogicalVolumes(); for (int x = lvs.size() - 1; x >= 0; x--) { // find and list any thin volumes if (lvs[x]->isThinPool()) lvs.append(lvs[x]->getThinVolumes()); } for (int x = lvs.size() - 1; x >= 0; x--) { // find and list any snapshots if (lvs[x]->isCowOrigin()) { LvList snaps(lvs[x]->getSnapshots()); for (int y = snaps.size() - 1; y >= 0; y--) { if (snaps[y]->isCowSnap()) lvs.append(snaps[y]); } } } return lvs; } // Returns all the pvs associated with this volume including the pvs // of any snapshots under it and for the pool if it is a thin volume. QStringList VGSplitDialog::getPvs(LogVol *const lv) { QStringList names; if (lv->isThinVolume()) names = m_vg->getLvByName(lv->getPoolName())->getPvNamesAllFlat(); else names = lv->getPvNamesAllFlat(); if (lv->isCowOrigin()) { if (lv->getParent() != nullptr && !lv->isSnapContainer()) names.append(lv->getParent()->getPvNamesAllFlat()); else names.append(lv->getPvNamesAllFlat()); } names.removeDuplicates(); return names; } kvpm-0.9.10/kvpm/pvactions.cpp0000644000175000017500000001005012770324126016471 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvactions.h" #include "kvpmdialog.h" #include "physvol.h" #include "processprogress.h" #include "pvchange.h" #include "pvtree.h" #include "pvmove.h" #include "topwindow.h" #include "vgreduce.h" #include "volgroup.h" #include #include #include PVActions::PVActions(VolGroup *const group, QWidget *parent) : KActionCollection(parent), m_vg(group) { QAction *const pv_move = addAction("pvmove"); pv_move->setText(i18n("Move all physical extents")); pv_move->setIconText(i18n("Move all")); pv_move->setToolTip(i18n("Move all extents to another physical volume")); pv_move->setIcon(QIcon::fromTheme(QStringLiteral("lorry_go"))); QAction *const vg_reduce = addAction("pvremove"); vg_reduce->setText(i18n("Remove from volume group")); vg_reduce->setIconText(i18n("Remove")); vg_reduce->setToolTip(i18n("Remove physical volume from volume group")); vg_reduce->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); QAction *const pv_change = addAction("pvchange"); pv_change->setText(i18n("Change physical volume attributes")); pv_change->setIconText(i18n("attributes")); pv_change->setToolTip(i18n("Change physical volume attributes")); pv_change->setIcon(QIcon::fromTheme(QStringLiteral("wrench_orange"))); setActions(nullptr, false); connect(this, SIGNAL(actionTriggered(QAction *)), this, SLOT(callDialog(QAction *))); } void PVActions::setActions(PhysVol *const pv, bool const isMoving) { m_pv = pv; QAction *const pv_move = action("pvmove"); QAction *const vg_reduce = action("pvremove"); QAction *const pv_change = action("pvchange"); if (!pv || m_vg->isExported()) { pv_move->setEnabled(false); vg_reduce->setEnabled(false); pv_change->setEnabled(false); } else { pv_move->setEnabled(true); vg_reduce->setEnabled(true); pv_change->setEnabled(true); if (pv->getSize() == pv->getRemaining()) { // pv has no extents in use pv_move->setEnabled(false); if (m_vg->getPvCount() > 1) vg_reduce->setEnabled(true); else vg_reduce->setEnabled(false); // can't remove last pv from group } else { vg_reduce->setEnabled(false); if (m_vg->getPvCount() > 1) { // can't move extents if there isn't another volume to put them on if (isMoving) // can't have more than one pvmove pv_move->setEnabled(false); // See physvol.cpp about removing this else pv_move->setEnabled(true); } else { pv_move->setEnabled(false); } } } } void PVActions::changePv(QTreeWidgetItem *item) { PhysVol *pv = nullptr; bool ismoving = false; if (item) { pv = m_vg->getPvByName(item->data(0, 0).toString()); ismoving = QVariant(item->data(7, 0)).toString().contains("pvmove"); } setActions(pv, ismoving); } void PVActions::callDialog(QAction *action) { if (m_pv) { KvpmDialog *dialog = nullptr; if (action->objectName() == "pvmove") dialog = new PVMoveDialog(m_pv); else if (action->objectName() == "pvchange") dialog = new PVChangeDialog(m_pv); else if (action->objectName() == "pvremove") dialog = new VGReduceDialog(m_pv); if (dialog) { int result = dialog->run(); if (result == QDialog::Accepted || result == KDialog::Yes) g_top_window->reRun(); dialog->deleteLater(); } } } kvpm-0.9.10/kvpm/mkfs.h0000644000175000017500000000503612770324126015100 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef MKFS_H #define MKFS_H #include #include #include #include "kvpmdialog.h" class QComboBox; class QLineEdit; class QSpinBox; class QTabWidget; class LogVol; class StoragePartition; class MkfsDialog : public KvpmDialog { Q_OBJECT long m_stride_size; // stride size is in bytes here, not fs blocks int m_stride_count; QTabWidget *m_tab_widget; QGroupBox *m_stripe_box, *m_base_options_box, *m_ext4_options_box, *m_misc_options_box; QRadioButton *ext2, *ext3, *ext4, *reiser, *reiser4, *ntfs, *jfs, *xfs, *vfat, *swap, *btrfs, *wipefs; QSpinBox *m_reserved_spin; // space reserved for root processes QComboBox *m_block_combo; // blocksize QComboBox *m_inode_combo; // inode size QLineEdit *m_name_edit; // volume name QLineEdit *m_inode_edit; // bytes / inode QLineEdit *m_total_edit; // total inode count QLineEdit *m_stride_edit; // stride size QLineEdit *m_count_edit; // strides per stripe QCheckBox *m_extent_check; QCheckBox *m_ext_attr_check; QCheckBox *m_resize_inode_check; QCheckBox *m_dir_index_check; QCheckBox *m_filetype_check; QCheckBox *m_sparse_super_check; QCheckBox *m_wipe_fs_check; QCheckBox *m_flex_bg_check; QCheckBox *m_huge_file_check; QCheckBox *m_uninit_bg_check; QCheckBox *m_dir_nlink_check; QCheckBox *m_extra_isize_check; QCheckBox *m_lazy_itable_init_check; QString m_path; QWidget *generalTab(const long long size); QWidget *advancedTab(); QWidget *ext4Tab(); QGroupBox *miscOptionsBox(); QGroupBox *baseOptionsBox(); QGroupBox *ext4OptionsBox(); QGroupBox *stripeBox(); void wipeFilesystem(); bool hasInitialErrors(const bool mounted); void buildDialog(const long long size); private slots: void enableOptions(bool); void commit(); void adjustStrideEdit(int index); public: explicit MkfsDialog(LogVol *const volume, QWidget *parent = nullptr); explicit MkfsDialog(StoragePartition *const partition, QWidget *parent = nullptr); }; #endif kvpm-0.9.10/kvpm/pvextend.cpp0000644000175000017500000000136712770324126016333 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvextend.h" #include #include #include "processprogress.h" #include "storagepartition.h" bool pv_extend(QString path) { QStringList arguments; arguments << "pvresize" << path; ProcessProgress pv_grow(arguments); if (pv_grow.exitCode()) return false; else return true; } kvpm-0.9.10/kvpm/vginfolabels.cpp0000644000175000017500000001362012770324126017144 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vginfolabels.h" #include #include #include #include #include #include #include "misc.h" #include "volgroup.h" VGInfoLabels::VGInfoLabels(VolGroup *const group, QWidget *parent) : QFrame(parent) { QLabel *extent_size_label, *size_label, *used_label, *free_label, *lvm_fmt_label, *resizable_label, *clustered_label, *allocatable_label, *max_lv_label, *max_pv_label, *policy_label, *mda_label, *uuid_label; setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); setLineWidth(2); QString clustered, resizable; QVBoxLayout *upper_layout = new QVBoxLayout(); QHBoxLayout *hlayout1 = new QHBoxLayout(); QHBoxLayout *hlayout2 = new QHBoxLayout(); upper_layout->setMargin(0); hlayout1->setMargin(0); hlayout2->setMargin(0); hlayout1->setSpacing(0); hlayout2->setSpacing(0); upper_layout->addLayout(hlayout1); upper_layout->addLayout(hlayout2); QVBoxLayout *vlayout1 = new QVBoxLayout(); QVBoxLayout *vlayout2 = new QVBoxLayout(); QVBoxLayout *vlayout3 = new QVBoxLayout(); QVBoxLayout *vlayout4 = new QVBoxLayout(); QVBoxLayout *vlayout5 = new QVBoxLayout(); QVBoxLayout *vlayout6 = new QVBoxLayout(); QVBoxLayout *vlayout7 = new QVBoxLayout(); QWidget *label_widget1 = new QWidget(); QWidget *label_widget2 = new QWidget(); QWidget *label_widget3 = new QWidget(); QWidget *label_widget4 = new QWidget(); QWidget *label_widget5 = new QWidget(); QWidget *label_widget6 = new QWidget(); QWidget *label_widget7 = new QWidget(); label_widget1->setBackgroundRole(QPalette::Base); label_widget1->setAutoFillBackground(true); label_widget2->setBackgroundRole(QPalette::AlternateBase); label_widget2->setAutoFillBackground(true); label_widget3->setBackgroundRole(QPalette::Base); label_widget3->setAutoFillBackground(true); label_widget4->setBackgroundRole(QPalette::AlternateBase); label_widget4->setAutoFillBackground(true); label_widget5->setBackgroundRole(QPalette::Base); label_widget5->setAutoFillBackground(true); label_widget6->setBackgroundRole(QPalette::AlternateBase); label_widget6->setAutoFillBackground(true); label_widget7->setBackgroundRole(QPalette::Base); label_widget7->setAutoFillBackground(true); label_widget1->setLayout(vlayout1); label_widget2->setLayout(vlayout2); label_widget3->setLayout(vlayout3); label_widget4->setLayout(vlayout4); label_widget5->setLayout(vlayout5); label_widget6->setLayout(vlayout6); label_widget7->setLayout(vlayout7); if (group->isResizable()) resizable = "Yes"; else resizable = "No"; if (group->isClustered()) clustered = "Yes"; else clustered = "No"; KConfigSkeleton skeleton; bool use_si_units; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", use_si_units, false); KFormat::BinaryUnitDialect dialect; if (use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; used_label = new QLabel(i18nc("Space used up", "Used: %1", KFormat().formatByteSize(group->getUsedSpace(), 1, dialect))); free_label = new QLabel(i18nc("Space not used", "Free: %1", KFormat().formatByteSize(group->getFreeSpace(), 1, dialect))); size_label = new QLabel(i18nc("Total space on device", "Total: %1", KFormat().formatByteSize(group->getSize(), 1, dialect))); lvm_fmt_label = new QLabel(i18n("Format: %1", group->getFormat())); policy_label = new QLabel(i18n("Policy: %1", policyToLocalString(group->getPolicy()))); resizable_label = new QLabel(i18n("Resizable: %1", resizable)); clustered_label = new QLabel(i18n("Clustered: %1", clustered)); allocatable_label = new QLabel(i18n("Allocatable: %1", KFormat().formatByteSize(group->getAllocatableSpace(), 1, dialect))); extent_size_label = new QLabel(i18n("Extent size: %1", KFormat().formatByteSize(group->getExtentSize(), 1, dialect))); mda_label = new QLabel(i18n("MDA: %1 Used: %2", group->getMdaCount(), group->getMdaUsed())); uuid_label = new QLabel(i18n("UUID: %1", group->getUuid())); uuid_label->setWordWrap(true); vlayout1->addWidget(size_label); vlayout1->addWidget(allocatable_label); vlayout2->addWidget(used_label); vlayout2->addWidget(free_label); vlayout3->addWidget(clustered_label); vlayout3->addWidget(lvm_fmt_label); vlayout4->addWidget(extent_size_label); vlayout4->addWidget(policy_label); vlayout5->addWidget(resizable_label); vlayout5->addWidget(mda_label); vlayout6->addWidget(uuid_label); hlayout1->addWidget(label_widget1); hlayout1->addWidget(label_widget2); hlayout1->addWidget(label_widget3); hlayout1->addWidget(label_widget4); hlayout1->addWidget(label_widget5); hlayout1->addWidget(label_widget6); if (group->getPvMax()) max_pv_label = new QLabel(i18n("Max pvs: %1", group->getPvMax())); else max_pv_label = new QLabel(i18n("Max pvs: Unlimited")); if (group->getLvMax()) max_lv_label = new QLabel(i18n("Max lvs: %1", group->getLvMax())); else max_lv_label = new QLabel(i18n("Max lvs: Unlimited")); vlayout7->addWidget(max_pv_label); vlayout7->addWidget(max_lv_label); hlayout1->addWidget(label_widget7); if (!group->getLvMax() && !group->getPvMax()) label_widget7->hide(); setLayout(upper_layout); } kvpm-0.9.10/kvpm/fsdata.cpp0000644000175000017500000000235112770324126015732 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "fsdata.h" #include #include #include #include #include #include "misc.h" // Gets basic stats on mounted filesystems like amount used up. FSData get_fs_data(const QString path) { struct statvfs buff; const QByteArray path_qba = path.toLocal8Bit(); const int error = statvfs(path_qba.data(), &buff); FSData fs_data; if (error) { fs_data.block_size = -1; fs_data.size = -1; fs_data.used = -1; } else { const long long block_size = buff.f_bsize; const long long frag_size = buff.f_frsize; const long long total_blocks = (frag_size * buff.f_blocks) / block_size; fs_data.block_size = block_size; fs_data.size = total_blocks; fs_data.used = total_blocks - buff.f_bavail; } return fs_data; } kvpm-0.9.10/kvpm/pvreduce.cpp0000644000175000017500000000167612770324126016316 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvreduce.h" #include #include "processprogress.h" // Returns new pv size in bytes or 0 if no shrinking was done // Takes new_size in bytes. long long pv_reduce(QString path, long long new_size) { QStringList arguments; QString size_string; arguments << "pvresize" << "--setphysicalvolumesize" << QString("%1m").arg(new_size / (1024 * 1024)) << path; ProcessProgress pv_shrink(arguments); if (pv_shrink.exitCode()) return 0; else return new_size; } kvpm-0.9.10/kvpm/devicepropertiesstack.h0000644000175000017500000000166212770324126020543 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef DEVICEPROPERTIESSTACK_H #define DEVICEPROPERTIESSTACK_H #include #include class QTreeWidgetItem; class StorageDevice; class DevicePropertiesStack : public QStackedWidget { Q_OBJECT QStringList m_device_path_list; // full path of each device on the stack, in the same order QWidget *getDefaultWidget(); public: explicit DevicePropertiesStack(QWidget *parent = 0); void loadData(QList devices); public slots: void changeDeviceStackIndex(QTreeWidgetItem *item); }; #endif kvpm-0.9.10/kvpm/vgexport.h0000644000175000017500000000075212770324126016016 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGEXPORT_H #define VGEXPORT_H class VolGroup; bool import_vg(VolGroup *const volumeGroup); #endif kvpm-0.9.10/kvpm/devicesizechart.h0000644000175000017500000000225412770324126017313 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2014 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef STORAGEDEVICESIZECHART_H #define STORAGEDEVICESIZECHART_H #include #include #include #include #include class DeviceSizeChart : public QFrame { Q_OBJECT QHBoxLayout *m_layout; QHBoxLayout *m_extended_layout; // The layout for chart segments inside an extended partition QList m_segments, m_extended_segments; // Segments of the bar chart, not the disk. QList m_ratios, m_extended_ratios; public: DeviceSizeChart(QWidget *const parent); void resizeEvent(QResizeEvent *const event); void resizeSegments(const int width); public slots: void setNewDevice(QTreeWidgetItem *deviceItem); signals: void deviceMenuRequested(QTreeWidgetItem *item); }; #endif kvpm-0.9.10/kvpm/resync.h0000644000175000017500000000072112770324126015437 0ustar benscottbenscott/* * * * Copyright (C) 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef RESYNC_H #define RESYNC_H class LogVol; bool resync(LogVol *const lv); #endif kvpm-0.9.10/kvpm/misc.h0000644000175000017500000000435112770324126015072 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef MISC_H #define MISC_H #include #include #include #include #include class PhysVol; class StorageBase; typedef enum { LINEAR = 0, LVMMIRROR = 1, RAID1 = 2, RAID4 = 3, RAID5 = 4, RAID6 = 5, THIN = 6 } VolumeType; struct PvSpace { PhysVol *pv; long long normal; long long contiguous; PvSpace(PhysVol *volume, long long norm, long long cont) {pv = volume; normal = norm; contiguous = cont;} }; typedef QList> PvSpaceList; QStringList splitUuid(QString const uuid); bool isBusy(const QString device); QString findMapperPath(QString path); QList getUsablePvs(); class NoMungeCheck : public QCheckBox { QString m_unmunged_text; // QCheckBox text() without amperands QString m_alternate_text; // We can put anything we want in here QStringList m_alternate_text_list; // Ditto QVariant m_data; public: explicit NoMungeCheck(const QString text, QWidget *parent = nullptr); QString getAlternateText(); QStringList getAlternateTextList(); QString getUnmungedText(); void setAlternateText(QString alternateText); void setAlternateTextList(QStringList alternateTextList); void setData(QVariant data); QVariant getData(); }; class NoMungeRadioButton : public QRadioButton { QString m_unmunged_text; // QCheckBox text() without amperands QString m_alternate_text; // We can put anything we want in here QVariant m_data; public: explicit NoMungeRadioButton(const QString text, QWidget *parent = nullptr); QString getAlternateText(); QString getUnmungedText(); void setAlternateText(QString alternateText); void setData(QVariant data); QVariant getData(); }; #endif kvpm-0.9.10/kvpm/storagebase.cpp0000644000175000017500000000541212770324126016770 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include #include #include #include "physvol.h" #include "storagebase.h" StorageBase::StorageBase(PedPartition *const part, const QList &pvList, const QStringList mdblock) { m_sector_size = part->disk->dev->sector_size; char *const ped_path = ped_partition_get_path(part); m_name = QString(ped_path).trimmed(); free(ped_path); m_is_writable = !part->disk->dev->read_only; commonConstruction(pvList); if (m_is_pv) m_is_busy = m_pv->isActive(); else m_is_busy = ped_partition_is_busy(part); m_is_dmraid = false; m_is_dmraid_block = false; m_is_mdraid = false; m_is_mdraid_block = false; for (auto dev : mdblock) { if (dev == m_name) { m_is_mdraid_block = true; break; } } } StorageBase::StorageBase(PedDevice *const device, const QList &pvList, const QStringList dmblock, const QStringList dmraid, const QStringList mdblock, const QStringList mdraid) { m_sector_size = device->sector_size; m_name = QString(device->path).trimmed(); m_is_writable = !device->read_only; commonConstruction(pvList); m_is_busy = ped_device_is_busy(device); m_is_dmraid = false; m_is_dmraid_block = false; m_is_mdraid = false; m_is_mdraid_block = false; for (auto dev : dmblock) { if (dev == m_name) { m_is_dmraid_block = true; break; } } for (auto dev : dmraid) { if (dev == m_name) { m_is_dmraid = true; break; } } for (auto dev : mdblock) { if (dev == m_name) { m_is_mdraid_block = true; break; } } for (auto dev : mdraid) { if (dev == m_name) { m_is_mdraid = true; break; } } } void StorageBase::commonConstruction(const QList &pvList) { m_is_pv = false; m_pv = NULL; for (auto *pv : pvList) { if (m_name == pv->getMapperName()) { m_pv = pv; m_is_pv = true; } } KDE_struct_stat buf; QByteArray path = m_name.toLocal8Bit(); if (KDE_stat(path.data(), &buf) == 0) { m_major = major(buf.st_rdev); m_minor = minor(buf.st_rdev); } else { m_major = -1; m_minor = -1; } } kvpm-0.9.10/kvpm/logvol.h0000644000175000017500000002705112770324126015443 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LOGVOL_H #define LOGVOL_H #include #include #include #include "allocationpolicy.h" #include "misc.h" #include "mountentry.h" #include "mounttables.h" class LogVol; class VolGroup; class Segment; // This class takes the handles for volumes provided // by liblvm2app and converts the information into // a more Qt/KDE friendly form. typedef QList LvList; typedef QSharedPointer SmrtLvPtr; class LogVol { const VolGroup *const m_vg; QList m_segments; MountList m_mount_entries; QList m_lv_children; // For a mirror the children are the legs and log // Snapshots are also children -- see m_snap_container // RAID Metadata is here too MountTables *const m_tables; LogVol *m_lv_parent; // NULL if this is the 'top' lv QString m_lv_full_name; // volume_group/logical_volume QString m_lv_name; // name of this logical volume QString m_lv_mapper_path; // full path to volume, ie: /dev/vg1/lvol1 QString m_lv_fs; // Filesystem on volume, if known QString m_lv_fs_label; // Filesystem label or name QString m_lv_fs_uuid; // Filesystem uuid QString m_origin; // the origin if this is a snapshot QString m_pool; // the name of the thin pool if this is a thin volume, otherwise empty QString m_log; // The mirror log, if this is a mirror QString m_type; // the type of volume QString m_state; // the lv state AllocationPolicy m_policy; QString m_uuid; QStringList m_tags; double m_snap_percent; // the percentage used, if this is a snapshot double m_copy_percent; // the percentage of extents moved, if pvmove underway double m_data_percent; // the percentage of extents used by thin volumes long long m_size; // size in bytes long long m_total_size; // size in bytes, size of all children (mirror legs/logs and snaps) added together long long m_extents; // size in extents long long m_fs_size; // fs size in bytes long long m_fs_used; // bytes used up in fs int m_seg_total; // total number of segments in logical volume unsigned long m_major_device; // Unix device major number, if set unsigned long m_minor_device; // Unix device minor number, if set int m_log_count; // if a mirror -- how many logs int m_mirror_count; // if mirror -- how many legs bool m_partial; // Is missing one or more physical volumes bool m_zero; // Newly-allocated data blocks are overwritten with blocks of zeroes before use bool m_virtual; // Is virtual volume bool m_under_conversion; // Is going to be a mirrored volume bool m_raidmirror; // Is a raid 1 mirrored volume bool m_raidmirror_leg; // Is one of the underlying legs of a mirrored volume bool m_lvmmirror; // Is an LVM (not raid 1) mirrored volume bool m_lvmmirror_leg; // Is one of the underlying legs of a mirrored volume bool m_lvmmirror_log; // Is the log for a mirrored volume bool m_metadata; // Is RAID or thin pool metadata -- we get this from the lvs attribute flags bool m_raid_metadata; // Is RAID metadata -- we get this from the name ie: *_rmeta_* bool m_thin_metadata; // Is thin pool metadata -- we get this from the name ie: *_tmeta_* bool m_raid; // Is a raid volume, including raid 1 mirrors bool m_raid_image; // Is a raid device under a raid volume bool m_fixed, m_persistent; // fix the device minor and major number bool m_synced; // mirror or raid fully synced bool m_alloc_locked; // allocation type is fixed when pvmove is underway // (and maybe other times) bool m_active; bool m_mounted; // has a mounted filesystem bool m_open; // device is open const bool m_orphan; // virtual device with no pvs bool m_pvmove; // is a pvmove temporary volume bool m_cow_snap; // is a traditional snapshot volume bool m_thin_snap; // is a thin snapshot volume bool m_snap_container; // is a fake lv that contains the real lv and its snapshots as children bool m_is_origin; // is the origin of a 'COW' type snap. bool m_writable; bool m_valid; // is a valid snap bool m_merging; // is snap or snap origin that is merging bool m_thin; // is thin volume or a thin snapshot bool m_thin_data; // is thin pool data bool m_thin_pool; bool m_temp; // A temporary mirror created while converting a mirror void countLegsAndLogs(); void processSegments(lv_t lvmLV, QByteArray flags); void processMounts(); void setSnapContainer(vg_t lvmVg, lv_t lvmLV); void setPolicy(const char flag2); QStringList removePvNames(); // list 'devices' that are really sub lvs QStringList getMetadataNames(); // names of sub lvs that are metadata for this lv QStringList getPoolVolumeNames(vg_t lvmVg); QList getLvmSnapshots(vg_t lvmVg); void insertChildren(lv_t lvmLV, vg_t lvmVg); void calculateTotalSize(); void getDeviceNumbers(unsigned long &major, unsigned long &minor); public: LogVol(lv_t lvmLV, vg_t lvmVg, const VolGroup *const vg, LogVol *const lvParent, MountTables *const tables, bool orphan = false); ~LogVol(); void rescan(lv_t lvmLv, vg_t lvmVg); LvList getChildren() const; // just the children -- not grandchildren etc. LvList getAllChildrenFlat() const; // All children, grandchildren etc. un-nested. LvList getSnapshots() const; // This will work the same for snapcontainers or the real lv LvList getThinVolumes() const; // Thin logical volumes under a thin pool LvList getThinDataVolumes() const; // Data volumes supporting a thin pool LvList getThinMetadataVolumes() const; // Metadata volumes for a thin pool LvList getRaidImageVolumes() const; // Image volumes supporting a RAID volume LvList getRaidMetadataVolumes()const; // Metadata for a RAID volume LogVol * getParent() const { return m_lv_parent; } // NULL if this is a "top level" lv LogVol * getParentMirror(); // NULL if this is not a mirror component LogVol * getParentRaid(); // NULL if this is not a RAID type component LogVol * getRaidImageMetadata() const; // NULL if this is not a RAID image LogVol * getRaidMetadataImage() const; // NULL if this is not RAID metadata const VolGroup* getVg() const { return m_vg; } QString getName() const { return m_lv_name; } QString getPoolName() const { return m_pool; } // Name of this volume's thin pool if it is a thin volume, empty otherwise QString getFullName() const { return m_lv_full_name; } QString getFilesystem() const { return m_lv_fs; } QString getFilesystemLabel() const { return m_lv_fs_label; } QString getFilesystemUuid() const { return m_lv_fs_uuid; } QString getMapperPath() const { return m_lv_mapper_path; } AllocationPolicy getPolicy() const { return m_policy; } QString getState() const { return m_state; } QString getType() const { return m_type; } int getRaidType() const; QString getOrigin() const { return m_origin; } // The name of the parent volume to a snapshot QString getUuid() const { return m_uuid; } int getSegmentCount() const { return m_seg_total; } int getSegmentStripes(const int segment); int getSegmentStripeSize(const int segment); long long getSegmentSize(const int segment); long long getSegmentExtents(const int segment); QList getSegmentStartingExtent(const int segment); QStringList getPvNames(const int segment) const; QStringList getPvNamesAll() const; // full path of physical volumes for all segments QStringList getPvNamesAllFlat() const; // full path of physical volumes including child lvs, un-nested MountList getMountEntries() const { return m_mount_entries; } QStringList getMountPoints() const; QString getFstabMountPoint() { return m_tables->getFstabMountPoint(this); } QStringList getTags() const { return m_tags; } QString getDiscards(int segment) const; long long getSpaceUsedOnPv(const QString pvname) const; long long getMissingSpace() const; // space used on pvs that are missing long long getChunkSize(int segment) const; long long getExtents() const { return m_extents; } long long getSize() const { return m_size; } long long getTotalSize() const { return m_total_size; } long long getFilesystemSize() const { return m_fs_size; } long long getFilesystemUsed() const { return m_fs_used; } double getSnapPercent() const; double getCopyPercent() const; double getDataPercent() const; unsigned long getMinorDevice() const { return m_minor_device; } unsigned long getMajorDevice() const { return m_major_device; } int getLogCount() const { return m_log_count; } // RAID 1 returns 0 since it doesn't have separate logs int getMirrorCount() const { return m_mirror_count; } int getSnapshotCount() { return getSnapshots().size(); } bool isActive() const { return m_active; } bool isFixed() const { return m_fixed; } bool isLocked() const { return m_alloc_locked; } bool isMerging() const { return m_merging; } bool isMetadata() const { return m_metadata; } bool isRaidMetadata() const { return m_raid_metadata; } bool isThinMetadata() const { return m_thin_metadata; } bool isMirror() const { return (m_lvmmirror || m_raidmirror); } bool isMirrorLeg() const { return (m_lvmmirror_leg || m_raidmirror_leg); } bool isLvmMirror() const { return m_lvmmirror; } bool isLvmMirrorLeg() const { return m_lvmmirror_leg; } bool isLvmMirrorLog() const { return m_lvmmirror_log; } bool isMounted() const { return m_mounted; } bool isOpen() const { return m_open; } bool isCowOrigin() const { return m_is_origin; } bool isOrphan() const { return m_orphan; } bool isPersistent() const { return m_persistent; } bool isPvmove() const { return m_pvmove; } bool isRaid() const { return m_raid; } bool isRaidImage() const { return m_raid_image; } bool isCowSnap() const { return m_cow_snap; } bool isThinSnap() const { return m_thin_snap; } bool isSnapContainer() const { return m_snap_container; } bool isSynced() const; bool isTemporary() const { return m_temp; } bool isThinVolume() const { return m_thin; } bool isThinPool() const { return m_thin_pool; } bool isThinPoolData() const { return m_thin_data; } bool isUnderConversion() const { return m_under_conversion; } bool isValid() const { return m_valid; } bool isVirtual() const { return m_virtual; } bool isWritable() const { return m_writable; } bool isPartial() const { return m_partial; } bool willZero() const { return m_zero; } }; #endif kvpm-0.9.10/kvpm/vgimport.cpp0000644000175000017500000000166012770324126016341 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgimport.h" #include #include #include "logvol.h" #include "processprogress.h" #include "volgroup.h" bool import_vg(VolGroup *const volumeGroup) { QStringList args; const QString message = i18n("Import volume group: %1?", volumeGroup->getName()); if (KMessageBox::questionYesNo(0, message) == KMessageBox::Yes) { args << "vgimport" << volumeGroup->getName(); ProcessProgress remove(args); return true; } else { return false; } } kvpm-0.9.10/kvpm/unmount.cpp0000644000175000017500000001323512770324126016200 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "unmount.h" #include "logvol.h" #include "mountentry.h" #include "mounttables.h" #include "misc.h" #include "storagepartition.h" #include "volgroup.h" #include #include #include #include #include #include #include UnmountDialog::UnmountDialog(LogVol *const volume, QWidget *parent) : KvpmDialog(parent) { const MountList entries = volume->getMountEntries(); const QString name = volume->getName(); buildDialog(name, entries); } UnmountDialog::UnmountDialog(StoragePartition *const partition, QWidget *parent) : KvpmDialog(parent) { const MountList entries = partition->getMountEntries(); const QString name = partition->getName(); buildDialog(name, entries); } void UnmountDialog::buildDialog(QString const device, const MountList entries) { if (entries.isEmpty()) { preventExec(); hide(); KMessageBox::sorry(nullptr, i18n("Can not unmount: %1, it does not seem to be mounted", device)); return; } else if (entries.size() == 1) { m_single = true; if (entries[0]->getMountPosition() > 1) { preventExec(); hide(); KMessageBox::sorry(nullptr, i18n("Can not unmount: %1, another volume or " "device is mounted over the same " "mount point and must be unmounted first", device)); return; } } else { m_single = false; bool can_unmount = false; QListIterator entry(entries); while (entry.hasNext()) { if (entry.next()->getMountPosition() < 2) can_unmount = true; } if (!can_unmount) { preventExec(); KMessageBox::sorry(nullptr, i18n("Can not unmount: %1, another volume or " "device is mounted over the same " "mount point and must be unmounted first", device)); return; } } setCaption(i18n("Unmount Filesystem")); m_mp = entries[0]->getMountPoint(); NoMungeCheck *check; bool checks_disabled = false; QWidget *dialog_body = new QWidget(this); setMainWidget(dialog_body); QLabel *label; QVBoxLayout *layout = new QVBoxLayout(); dialog_body->setLayout(layout); label = new QLabel(i18n("Unmount Filesystem")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); layout->addSpacing(10); if (m_single) { setButtons(KDialog::Yes | KDialog::No); layout->addWidget(new QLabel(i18n("%1 is mounted on: %2", device, m_mp))); layout->addWidget(new QLabel(i18n("Do you wish to unmount it?"))); } else { setButtons(KDialog::Ok | KDialog::Cancel); enableButtonOk(false); label = new QLabel(i18n("%1 is mounted at multiple locatations.", device)); layout->addWidget(label); label = new QLabel(i18n("Select the ones to unmount:")); layout->addWidget(label); for (int x = 0; x < entries.size(); ++x) { check = new NoMungeCheck(entries[x]->getMountPoint()); if (entries[x]->getMountPosition() > 1) { check->setChecked(false); check->setEnabled(false); checks_disabled = true; } layout->addWidget(check); m_check_list.append(check); connect(check, SIGNAL(clicked()), this, SLOT(resetOkButton())); } if (checks_disabled) { label = new QLabel(i18n("Some selections have been disabled. Another " "device or volume is mounted over the same " "mount point and must be unmounted first")); label->setWordWrap(true); layout->addWidget(label); } } } void UnmountDialog::resetOkButton() { bool enable = false; QListIterator itr(m_check_list); while (itr.hasNext()) { if (itr.next()->isChecked()) enable = true; } enableButtonOk(enable); } void UnmountDialog::commit() { NoMungeCheck *cb; QListIterator cb_itr(m_check_list); QByteArray mp_qba; hide(); if (m_single) { mp_qba = m_mp.toLocal8Bit(); if (umount2(mp_qba.data(), 0)) { KMessageBox::error(nullptr, i18n("Unmounting %1 failed with error number: %2 %3", m_mp, errno, QString(strerror(errno)))); } else { MountTables::removeEntry(m_mp); } } else { while (cb_itr.hasNext()) { cb = cb_itr.next(); if (cb->isChecked()) { mp_qba = cb->getUnmungedText().toLocal8Bit(); if (umount2(mp_qba.data(), 0)) { KMessageBox::error(nullptr, i18n("Unmounting %1 failed with error number: %2 %3", m_mp, errno, QString(strerror(errno)))); } else { MountTables::removeEntry(cb->getUnmungedText()); } } } } } kvpm-0.9.10/kvpm/fsdata.h0000644000175000017500000000106412770324126015377 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef FSDATA_H #define FSDATA_H class QString; struct FSData { long long size; long long used; long long block_size; }; FSData get_fs_data(const QString path); #endif kvpm-0.9.10/kvpm/fsextend.cpp0000644000175000017500000002037212770324126016313 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "fsextend.h" #include #include #include #include #include #include #include #include "executablefinder.h" #include "fsck.h" #include "fsblocksize.h" #include "mountentry.h" #include "mounttables.h" #include "processprogress.h" #include "progressbox.h" #include "topwindow.h" bool do_temp_mount(const QByteArray dev, const QByteArray fs, const QByteArray mp); // device path, filesystem and mount point void do_temp_unmount(const QByteArray mp); bool resize_jfs_mount(const QByteArray dev, const QByteArray mp); bool fs_can_extend(const QString fs, const bool mounted) { QString executable; if (fs == "ext2" || fs == "ext3" || fs == "ext4" || fs == "reiserfs" || fs == "ntfs" || fs == "xfs") { if (fs == "ext2" || fs == "ext3" || fs == "ext4") executable = "resize2fs"; else if (fs == "reiserfs") executable = "resize_reiserfs"; else if (fs == "ntfs") executable = "ntfsresize"; else if (fs == "xfs") executable = "xfs_growfs"; if (mounted && ("ntfs" == fs)) { return false; } else if (ExecutableFinder::getPath(executable).isEmpty()) { KMessageBox::sorry(nullptr, i18n("Executable: '%1' not found, this filesystem cannot be extended", executable)); return false; } else { return true; } } else if (fs == "jfs") { return true; } return false; } // The largest size the filesystem can extend to in bytes or -1 if the fs // type can't be extended. long long fs_max_extend(const QString dev, const QString fs, const bool mounted) { long long max = -1; if (fs_can_extend(fs, mounted)) { if (fs == "ext2" || fs == "ext3") { const long bs = get_fs_block_size(dev); if (bs == 1024) max = 0x20000000000 - 1; // 2 TiB else if (bs == 2048) max = 0x80000000000 - 1; // 8 TiB else max = 0x100000000000 - 1; // 16 TiB // } else if (fs == "ext4") { // max = 0x1000000000000000 - 1; // 1 EiB ext4 theoretical limit } else if (fs == "ext4") { max = 0x100000000000 - 1; // 16 TiB seems to be the current limit for the ext fs resize tools } else if (fs == "reiserfs") { max = 0x100000000000 - 1; // 16 TiB } else if (fs == "ntfs") { max = 0x100000000000 - 4096; // 16 TiB -- assumes small cluster size } else if (fs == "xfs") { max = 0x8000000000000000 - 1; // 8 EiB } else if (fs == "jfs") { max = 0x2000000000000 - 1; // 512 TiB -- assumes 512 Bytes block size } else { max = -1; } } return max; } // dev is the full path to the device or volume, fs == filesystem, mps == mountpoints bool fs_extend(const QString dev, const QString fs, const QStringList mps, const bool isLV) { bool isMounted = true; QString mp; if (mps.isEmpty()) isMounted = false; else mp = mps[0]; const QByteArray dev_qba = dev.toLocal8Bit(); const QByteArray fs_qba = fs.toLocal8Bit(); const QByteArray mp_qba = mp.toLocal8Bit(); QStringList args; if (isMounted) { if (!isLV) { KMessageBox::sorry(nullptr, i18n("Only logical volumes may be mounted during resize, not partitions.")); return false; } else if (fs != "ext2" && fs != "ext3" && fs != "ext4" && fs != "reiserfs" && fs != "jfs" && fs != "xfs") { KMessageBox::sorry(nullptr, i18n("Filesystem '%1' can not be extended while mounted.", fs)); return false; } } const QString err_msg = i18n("It appears that the filesystem on the device or volume was not extended. " "It will need to be extended before the additional space can be used."); if (fs == "ext2" || fs == "ext3" || fs == "ext4") { if (!isMounted) { if (!fsck(dev)) return false; } args << "resize2fs" << dev; ProcessProgress fs_grow(args); if (fs_grow.exitCode()) { KMessageBox::sorry(nullptr, err_msg); return false; } else { return true; } } else if (fs == "xfs") { if (isMounted) { args << "xfs_growfs" << mp; ProcessProgress fs_grow(args); if (fs_grow.exitCode()) { KMessageBox::error(nullptr, err_msg); return false; } else { return true; } } else { QTemporaryDir temp_mp; const QByteArray temp_mp_qba = temp_mp.path().toLocal8Bit(); if (do_temp_mount(dev_qba, fs_qba, temp_mp_qba)) { args << "xfs_growfs" << temp_mp_qba; ProcessProgress fs_grow(args); do_temp_unmount(temp_mp_qba); if (fs_grow.exitCode()) { KMessageBox::error(nullptr, err_msg); return false; } else { return true; } } else { KMessageBox::error(nullptr, err_msg); return false; } } } else if (fs == "jfs") { bool success = false; if (isMounted) { success = resize_jfs_mount(dev_qba, mp_qba); } else { QTemporaryDir temp_mp; const QByteArray temp_mp_qba = temp_mp.path().toLocal8Bit(); if (do_temp_mount(dev_qba, fs_qba, temp_mp_qba)) { success = resize_jfs_mount(dev_qba, temp_mp_qba); do_temp_unmount(temp_mp_qba); } } return success; } else if (fs == "reiserfs") { args << "resize_reiserfs" << "-fq" << dev; ProcessProgress fs_grow(args); if (fs_grow.exitCode()) { KMessageBox::error(nullptr, err_msg); return false; } else { return true; } } else if (fs == "ntfs") { args << "ntfsresize" << "--no-progress-bar" << "--force" << dev; ProcessProgress fs_grow(args); if (fs_grow.exitCode()) { KMessageBox::error(nullptr, err_msg); return false; } else { return true; } } return false; } bool do_temp_mount(const QByteArray dev, const QByteArray fs, const QByteArray mp) { const unsigned long options = 0; int error = 0; if ("xfs" == fs) error = mount(dev.data(), mp.data(), fs.data(), options, "nouuid"); else error = mount(dev.data(), mp.data(), fs.data(), options, nullptr); if (error) { KMessageBox::error(nullptr, QString("%1: Error number: %2 %3").arg("mount()").arg(errno).arg(strerror(errno))); return false; } return true; } void do_temp_unmount(const QByteArray mp) { if(umount2(mp, 0)) KMessageBox::error(nullptr, QString("%1: Error number: %2 %3").arg("umount()").arg(errno).arg(strerror(errno))); } bool resize_jfs_mount(const QByteArray dev, const QByteArray mp) { bool success = false; TopWindow::getProgressBox()->setRange(0, 2); TopWindow::getProgressBox()->setValue(1); TopWindow::getProgressBox()->setText("jfs resize"); qApp->setOverrideCursor(Qt::WaitCursor); if (mount(dev.data(), mp.data(), "jfs", MS_REMOUNT, "resize")) { qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, QString("%1: Error number: %2 %3").arg("mount()").arg(errno).arg(strerror(errno))); success = false; } else { qApp->restoreOverrideCursor(); success = true; } TopWindow::getProgressBox()->reset(); return success; } kvpm-0.9.10/kvpm/repairmissing.cpp0000644000175000017500000003004612770324126017346 0ustar benscottbenscott/* * * * Copyright (C) 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "repairmissing.h" #include "physvol.h" #include "pvgroupbox.h" #include "processprogress.h" #include "volgroup.h" #include #include #include #include #include #include #include RepairMissingDialog::RepairMissingDialog(LogVol *const volume, QWidget *parent) : KvpmDialog(parent), m_lv(volume) { if (m_lv->getParentMirror()) m_lv = m_lv->getParentMirror(); LvList children; const QString lv_name = m_lv->getName(); const bool is_raid = m_lv->isRaid(); const bool is_lvm = m_lv->isLvmMirror(); QList const pvs = getUsablePvs(); if (!m_lv->isPartial()) { preventExec(); KMessageBox::sorry(nullptr, i18n("This volume has no missing physical volumes")); } else if (pvs.isEmpty() && is_raid && !m_lv->isMirror()) { preventExec(); KMessageBox::sorry(nullptr, i18n("No suitable physical volumes found")); } else { QHBoxLayout *const top_layout = new QHBoxLayout(); QVBoxLayout *const layout = new QVBoxLayout(); top_layout->addLayout(layout); QWidget *const main_widget = new QWidget(); QLabel *const lv_name_label = new QLabel(); if(is_lvm) { lv_name_label->setText(i18n("Repair mirror: %1", m_lv->getName())); setCaption(i18n("Repair Mirror")); } else if(is_raid) { lv_name_label->setText(i18n("Repair RAID device: %1", m_lv->getName())); setCaption(i18n("Repair RAID device")); } lv_name_label->setAlignment(Qt::AlignCenter); layout->addWidget(lv_name_label); layout->addSpacing(5); m_replace_radio = new QRadioButton(i18n("Replace missing physical volumes")); QRadioButton *const remove_radio = new QRadioButton(i18n("Remove missing physical volumes")); QVBoxLayout *const radio_layout = new QVBoxLayout(); QWidget *const radio_widget = new QWidget(); radio_layout->addWidget(m_replace_radio); radio_layout->addWidget(remove_radio); radio_widget->setLayout(radio_layout); layout->addWidget(radio_widget); QWidget *const physical = buildPhysicalWidget(pvs); if (pvs.isEmpty()) { QLabel *const icon_label = new QLabel(); icon_label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(64, 64)); top_layout->insertWidget(0, icon_label); QLabel *const warning_label = new QLabel(); warning_label->setText(QString("

%1
%2 %3?

") .arg(i18n("No suitable replacement physical volumes found.")) .arg(i18n("Remove missing physical volumes from:")) .arg(m_lv->getName())); layout->addSpacing(10); layout->addWidget(warning_label); m_replace_radio->setChecked(false); m_replace_radio->setEnabled(false); remove_radio->setChecked(true); remove_radio->setEnabled(false); radio_widget->hide(); physical->hide(); setButtons(KDialog::Yes | KDialog::No); setDefaultButton(KDialog::No); } else { m_replace_radio->setChecked(true); if (is_raid && !m_lv->isMirror()) { m_replace_radio->setEnabled(false); remove_radio->setEnabled(false); } connect(m_replace_radio, SIGNAL(toggled(bool)), this, SLOT(setReplace(bool))); resetOkButton(); } layout->addSpacing(5); layout->addWidget(physical); main_widget->setLayout(top_layout); setMainWidget(main_widget); } } /* The next function returns a list of physical volumes not already in use by unbroken parts of the volume since they should not be reused */ QList RepairMissingDialog::getUsablePvs() { LvList const images = m_lv->getAllChildrenFlat(); QStringList pvs_in_use; // Currently, available pvs on the broken legs can't be used for repair! // If that gets fixed we will need to calculate the free space on the // pvs *including what the broken image is using* since that will go away // and be replaced the the new image. The following commented code won't be // enough. /* if (((images[x]->isRaidImage() || images[x]->isMetadata())) && !images[x]->isPartial()) pvs_in_use << images[x]->getPvNamesAll(); */ if (m_lv->isMirror()) { for (int x = images.size() - 1; x >= 0; x--) { if (((images[x]->isMirrorLeg() || images[x]->isLvmMirrorLog()))) pvs_in_use << images[x]->getPvNamesAll(); } } else if (m_lv->isRaid()) { for (int x = images.size() - 1; x >= 0; x--) { if (((images[x]->isRaidImage() || images[x]->isMetadata()))) pvs_in_use << images[x]->getPvNamesAll(); } } else { pvs_in_use << m_lv->getPvNamesAll(); } QList unused_pvs = m_lv->getVg()->getPhysicalVolumes(); for (int x = unused_pvs.size() - 1; x >= 0; x--) { if (!unused_pvs[x]->isAllocatable() || unused_pvs[x]->getRemaining() <= 0 || unused_pvs[x]->isMissing()) { unused_pvs.removeAt(x); } else { for (int y = pvs_in_use.size() - 1; y >= 0; y--) { if (unused_pvs[x]->getMapperName() == pvs_in_use[y]) { unused_pvs.removeAt(x); break; } } } } return unused_pvs; } /* Here we create a string based on all the options that the user chose in the dialog and feed that to "lvconvert" */ QStringList RepairMissingDialog::arguments() { QStringList args; args << "lvconvert" << "--repair" << "-y"; // -y: answer all prompts with "yes" if (m_pv_box->getPolicy() <= ANYWHERE) // don't pass INHERITED_* args << "--alloc" << policyToString(m_pv_box->getEffectivePolicy()); if (!m_replace_radio->isChecked()) args << "-f" << m_lv->getFullName(); // no replace else args << m_lv->getFullName() << m_pv_box->getNames(); return args; } void RepairMissingDialog::commit() { hide(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); ProcessProgress repair_missing(arguments()); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } QWidget *RepairMissingDialog::buildPhysicalWidget(QList const pvs) { QWidget *const physical = new QWidget; QVBoxLayout *const layout = new QVBoxLayout(); QList> pv_space_list; for (auto pv : pvs) { pv_space_list << QSharedPointer(new PvSpace(pv, pv->getRemaining(), pv->getContiguous())); } m_pv_box = new PvGroupBox(pv_space_list, m_lv->getPolicy(), m_lv->getVg()->getPolicy()); layout->addWidget(m_pv_box); layout->addStretch(); QHBoxLayout *const h_layout = new QHBoxLayout(); QVBoxLayout *const lower_layout = new QVBoxLayout(); layout->addLayout(h_layout); h_layout->addStretch(); h_layout->addLayout(lower_layout); h_layout->addStretch(); physical->setLayout(layout); connect(m_pv_box, SIGNAL(stateChanged()), this, SLOT(resetOkButton())); return physical; } int RepairMissingDialog::getImageNumber(QString name) { int number = -1; bool ok = true; if (name.contains("_rmeta_") || (name.contains("_rimage_"))) { if (name.contains("_rmeta_")) name = name.remove(0, name.indexOf("_rmeta_") + 7); else name = name.remove(0, name.indexOf("_rimage_") + 8); number = name.toInt(&ok); if (!ok) number = -1; } return number; } void RepairMissingDialog::setReplace(const bool replace) { m_pv_box->selectNone(); m_pv_box->setEnabled(replace); } QList RepairMissingDialog::getSelectedPvs() { QList pvs; QStringList const pvnames = m_pv_box->getNames(); const VolGroup *const vg = m_lv->getVg(); for (int x = pvnames.size() - 1; x >= 0; x--) { PhysVol *const pv = vg->getPvByName(pvnames[x]); if (pv != nullptr) pvs.append(pv); } return pvs; } LvList RepairMissingDialog::getPartialLvs() { LvList partial = m_lv->getAllChildrenFlat(); for (int x = partial.size() - 1; x >= 0; x--) { if (!partial[x]->isPartial()) partial.removeAt(x); } return partial; } /* Enable or disable the OK button based on having enough physical volumes checked. */ void RepairMissingDialog::resetOkButton() { if (!m_replace_radio->isChecked()) { enableButtonOk(true); return; } LvList partial_lvs = getPartialLvs(); /* RETEST THIS WHEN NEW LVM VERSION IS INSTALLED As of: lvs --version LVM version: 2.02.98(2) (2012-10-15) Library version: 1.02.77 (2012-10-15) Driver version: 4.23.0 The allocation policy of lvconvert isn't using contiguous the same way as lvcreate and is probably being ignored. Anywhere policy not tested yet but should be implemented when contiguous is. */ /* AllocationPolicy policy = m_pv_box->getPolicy(); if (policy == INHERITED) policy = m_lv->getVg()->getPolicy(); */ QList missing_bytes; QList missing_log_bytes; const bool is_lvm = m_lv->isLvmMirror(); while (partial_lvs.size()) { LogVol *lv = partial_lvs.takeAt(0); long long space = lv->getSize(); if (is_lvm) { // We assume logs and mimages can be on the same pvs if (!lv->isTemporary()) { if (lv->isLvmMirrorLog() && !lv->isLvmMirror()) missing_log_bytes.append(space); else if (lv->isLvmMirrorLeg()) missing_bytes.append(space); } } else { // we try to put the metadata and corresponding the image/leg segments together here const int num = getImageNumber(lv->getName()); if (num >= 0) { for (int x = partial_lvs.size() - 1; x >= 0; x--) { if (num == getImageNumber(partial_lvs[x]->getName())) space += partial_lvs.takeAt(x)->getSize(); } } missing_bytes.append(space); } } if (is_lvm) { for (int x = missing_log_bytes.size() - 1; x >= 0; x--) { if (missing_bytes.size() > x) missing_bytes[x] += missing_log_bytes[x]; else missing_bytes.append(missing_log_bytes[x]); } } qSort(missing_bytes); for (int n = missing_bytes.size() - 1; n >= 0; n--) { if (missing_bytes[n] <= 0) missing_bytes.removeAt(n); } QList available_bytes; QList const pvs = getSelectedPvs(); for (int x = pvs.size() - 1; x >= 0; x--) { available_bytes.append(pvs[x]->getRemaining()); } qSort(available_bytes); if (missing_bytes.size()) { while (available_bytes.size()) { qSort(available_bytes); qSort(missing_bytes.begin(), missing_bytes.end(), qGreater()); missing_bytes[0] -= available_bytes.takeLast(); } qSort(missing_bytes.begin(), missing_bytes.end(), qGreater()); if (missing_bytes[0] > 0) enableButtonOk(false); else enableButtonOk(true); } else { enableButtonOk(true); } return; } kvpm-0.9.10/kvpm/deviceproperties.cpp0000644000175000017500000001776412770324126020062 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "deviceproperties.h" #include "misc.h" #include "mountentry.h" #include "physvol.h" #include "storagedevice.h" #include "storagepartition.h" #include #include #include #include DeviceProperties::DeviceProperties(StorageDevice *const device, QWidget *parent) : QWidget(parent) { QVBoxLayout *const layout = new QVBoxLayout(); layout->setSpacing(0); layout->setMargin(0); layout->addWidget(generalFrame(device)); layout->addWidget(hardwareFrame(device)); if (device->isPhysicalVolume()) layout->addWidget(pvFrame(device->getPhysicalVolume())); setLayout(layout); layout->addStretch(); } DeviceProperties::DeviceProperties(StoragePartition *const partition, QWidget *parent) : QWidget(parent) { QVBoxLayout *const layout = new QVBoxLayout(); layout->setSpacing(0); layout->setMargin(0); layout->addWidget(generalFrame(partition)); KConfigSkeleton skeleton; bool show_mount, show_fsuuid, show_fslabel; skeleton.setCurrentGroup("DeviceProperties"); skeleton.addItemBool("dp_mount", show_mount, true); skeleton.addItemBool("dp_fsuuid", show_fsuuid, false); skeleton.addItemBool("dp_fslabel", show_fslabel, false); if (partition->isMountable()) { if (show_mount) layout->addWidget(mpFrame(partition)); if (show_fsuuid || show_fslabel) layout->addWidget(fsFrame(partition, show_fsuuid, show_fslabel)); } else if (partition->isPhysicalVolume()) { layout->addWidget(pvFrame(partition->getPhysicalVolume())); } setLayout(layout); layout->addStretch(); } QFrame *DeviceProperties::generalFrame(StoragePartition *const partition) { QFrame *const frame = new QFrame; QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); QLabel *const name_label = new QLabel(QString("%1").arg(partition->getName())); name_label->setAlignment(Qt::AlignCenter); layout->addWidget(name_label); layout->addWidget(new QLabel(i18n("First sector: %1", partition->getTrueFirstSector()))); if (partition->isFreespace()) { layout->addWidget(new QLabel(i18n("First aligned: %1 (to 1 MiB)", partition->getFirstSector()))); } layout->addWidget(new QLabel(i18n("Last sector: %1", partition->getLastSector()))); if (partition->isNormal() || partition->isLogical() || partition->isExtended()) { layout->addWidget(new QLabel()); const QStringList flags = partition->getFlags(); layout->addWidget(new QLabel(i18n("Flags: %1", flags.join(", ")))); } frame->setLayout(layout); return frame; } QFrame *DeviceProperties::mpFrame(StoragePartition *const partition) { QFrame *const frame = new QFrame(); QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); auto entries = partition->getMountEntries(); QLabel *label; if (entries.size() <= 1) label = new QLabel(i18n("Mount Point")); else label = new QLabel(i18n("Mount Points")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); if (!entries.isEmpty()) { for (int x = 0; x < entries.size(); x++) { const QString mp = entries[x]->getMountPoint(); const int pos = entries[x]->getMountPosition(); if (entries[x]->getMountPosition() > 0) { label = new QLabel(mp + QString("<%1>").arg(pos)); label->setToolTip(mp + QString("<%1>").arg(pos)); layout->addWidget(label); } else { label = new QLabel(mp); label->setToolTip(mp); layout->addWidget(label); } } } else { layout->addWidget(new QLabel(i18n("Not mounted"))); } frame->setLayout(layout); return frame; } QFrame *DeviceProperties::generalFrame(StorageDevice *const device) { QFrame *frame = new QFrame; QVBoxLayout *layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); QLabel *name_label = new QLabel(QString("%1").arg(device->getName())); name_label->setAlignment(Qt::AlignCenter); layout->addWidget(name_label); if (device->isPhysicalVolume()) layout->addWidget(new QLabel(device->getDiskLabel())); else layout->addWidget(new QLabel(i18n("Partition table: %1", device->getDiskLabel()))); layout->addWidget(new QLabel(i18n("Logical sector size: %1", device->getSectorSize()))); layout->addWidget(new QLabel(i18n("Physical sector size: %1", device->getPhysicalSectorSize()))); layout->addWidget(new QLabel(i18n("Sectors: %1", device->getSize() / device->getSectorSize()))); if (!device->isWritable()) layout->addWidget(new QLabel(i18nc("May be read and not written", "Read only"))); else layout->addWidget(new QLabel(i18n("Read/write"))); if (device->isBusy()) layout->addWidget(new QLabel(i18n("Busy: Yes"))); else layout->addWidget(new QLabel(i18n("Busy: No"))); frame->setLayout(layout); return frame; } QFrame *DeviceProperties::fsFrame(StoragePartition *const partition, const bool showFsUuid, const bool showFsLabel) { QLabel *label; QFrame *const frame = new QFrame; QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); if (showFsLabel) { label = new QLabel(i18n("Filesystem Label")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); label = new QLabel(partition->getFilesystemLabel()); layout->addWidget(label); } if (showFsUuid) { label = new QLabel(i18n("Filesystem UUID")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); const QStringList uuid = splitUuid(partition->getFilesystemUuid()); layout->addWidget(new QLabel(uuid[0])); layout->addWidget(new QLabel(uuid[1])); } frame->setLayout(layout); return frame; } QFrame *DeviceProperties::pvFrame(PhysVol *const pv) { QFrame *const frame = new QFrame; QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); QLabel *label; label = new QLabel(i18n("Physical Volume")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); if (pv->isActive()) label = new QLabel(i18n("State: active")); else label = new QLabel(i18n("State: inactive")); layout->addWidget(label); label = new QLabel(i18n("UUID")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); const QStringList uuid = splitUuid(pv->getUuid()); layout->addWidget(new QLabel(uuid[0])); layout->addWidget(new QLabel(uuid[1])); frame->setLayout(layout); return frame; } QFrame *DeviceProperties::hardwareFrame(StorageDevice *const device) { QFrame *const frame = new QFrame; QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); QLabel *label; label = new QLabel(i18n("Hardware")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); label = new QLabel(device->getHardware()); label->setWordWrap(true); layout->addWidget(label); frame->setLayout(layout); return frame; } kvpm-0.9.10/kvpm/vgremove.h0000644000175000017500000000073412770324126015772 0ustar benscottbenscott/* * * * Copyright (C) 2008 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGREMOVE_H #define VGREMOVE_H class VolGroup; bool remove_vg(VolGroup *volumeGroup); #endif kvpm-0.9.10/kvpm/physvol.h0000644000175000017500000000610112770324126015636 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PHYSVOL_H #define PHYSVOL_H #include #include class VolGroup; class LogVol; // TODO -- the following struct should be accessed with smart pointers struct LVSegmentExtent { QString lv_name; long long first_extent; long long last_extent; }; class PhysVol { const VolGroup *const m_vg; // all pvs now must be in a vg QString m_device; // eg: /dev/hde4 QString m_mapper_device; // eg: /dev/mapper/foo if it is dmraid, equals m_device otherwise QString m_format; // e.g. lvm1 or lvm2 QString m_uuid; QStringList m_tags; bool m_active; bool m_allocatable; bool m_missing; // the physical volume can't be found uint64_t m_mda_count; // number of metadata areas uint64_t m_mda_used; // number of metadata areas in use uint64_t m_mda_size; long long m_size; // size in bytes of physical volume long long m_device_size; // size in bytes of underlying device long long m_unused; // free space in bytes long long m_last_used_extent; public: PhysVol(pv_t lvm_pv, const VolGroup *const vg); void rescan(pv_t pv); QString getMapperName() const { return m_mapper_device.trimmed(); } QString getUuid() const { return m_uuid.trimmed(); } QStringList getTags() const { return m_tags; } const VolGroup* getVg() const { return m_vg; } bool isAllocatable() const { return m_allocatable; } void setActive() { m_active = true; } // If any lv is active on the pv, the pv is active bool isActive() const { return m_active; } bool isMissing() const { return m_missing; } long long getContiguous(LogVol *lv) const; // the number of contiguous bytes available if the lv is on this pv long long getContiguous() const; // the max contiguous bytes on the the pv. long long getSize() const { return m_size; } // size of the physical volume in bytes long long getDeviceSize() const { return m_device_size; } // the physical volume might not take up all the device! long long getRemaining() const { return m_unused; } // free space in bytes long long getLastUsedExtent() const { return m_last_used_extent; } // needed for minimum shrink size determination void setLastUsedExtent(const long long last) { m_last_used_extent = last; } int getPercentUsed() const; // 0 - 100 long getMdaCount() const { return m_mda_count; } long getMdaUsed() const { return m_mda_used; } // Meta Data areas in use long long getMdaSize() const { return m_mda_size; } // Meta Data Area size in bytes QList sortByExtent() const; }; #endif kvpm-0.9.10/kvpm/partremove.h0000644000175000017500000000101112770324126016311 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PARTREMOVE_H #define PARTREMOVE_H class StoragePartition; bool remove_partition(StoragePartition *const partition); #endif kvpm-0.9.10/kvpm/lvcreatebase.h0000644000175000017500000001024512770324126016576 0ustar benscottbenscott/* * * * Copyright (C) 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVCREATEBASE_H #define LVCREATEBASE_H #include #include "kvpmdialog.h" #include "misc.h" class VolGroup; class QCheckBox; class QGroupBox; class QLabel; class QLineEdit; class QRegExpValidator; class QStringList; class QTabWidget; class QVBoxLayout; class SizeSelectorBox; class LvCreateDialogBase : public KvpmDialog { Q_OBJECT const VolGroup *const m_vg; QVBoxLayout *m_general_layout; bool m_use_si_units; // true == Metric SI sizes = MB and GB, otherise use MiB, GiB etc. bool m_extend; // true == we are extending a volume bool m_ispool; // true == creating or extending a thin pool SizeSelectorBox *m_size_selector; QRegExpValidator *m_name_validator, *m_tag_validator; QLineEdit *m_minor_edit, *m_major_edit, *m_name_edit, *m_tag_edit; QCheckBox *m_readonly_check, *m_monitor_check, // monitor with dmeventd *m_udevsync_check, // sync operation with udev *m_zero_check, // write zero at start of volume *m_skip_sync_check, // skip initial sync of mirror *m_extend_fs_check; // extend filesystem along with volume QGroupBox *m_persistent_box; QTabWidget *m_tab_widget; QLabel *m_max_size_label, *m_stripes_label, *m_maxfs_size_label, *m_warning_label, *m_current_label, *m_extend_label; long long m_max_size; // only used for setting the labels. long long m_maxfs_size; // can be retrieved and used by subclasses QWidget *m_warning_widget; QWidget *createWarningWidget(); QWidget* createGeneralTab(const bool showNameTag, const bool showRo, const bool showZero, const bool showMisc); QWidget* createAdvancedTab(const bool showPersistent, const bool showSkipSync, const bool showMonitor); signals: void extendFs(); private slots: void zeroReadOnlyEnable(); protected slots: virtual void resetOkButton() = 0; void setSizeLabels(); protected: const VolGroup *getVg(); virtual QStringList args() = 0; virtual long long getLargestVolume() = 0; virtual bool isValid(); // Has a valid size, name, major number, minor number and tag bool isLow(); // size too small void setSkipSync(const bool skip); void setReadOnly(const bool ro); void setZero(const bool zero); void setMonitor(const bool monitor); void setUdev(const bool udev); void initializeSizeSelector(const long long extentSize, const long long currentSize, const long long maxSize); void setPhysicalTab(QWidget *const tab); void enableMonitor(const bool monitor); void enableSkipSync(const bool skip); void enableReadOnly(const bool ro); void enableZero(const bool zero); void setInfoLabels(const VolumeType type, const int stripes, const int mirrors, const long long maxSize); void setWarningLabel(const QString message); void clearWarningLabel(); void setSelectorMaxExtents(const long long max); long long getSelectorExtents(); long long getMaxFsSize(); bool getExtendFs(); // is the extend fs check boxed checked bool getMonitor(); bool getUdev(); bool getReadOnly(); bool getSkipSync(); bool getZero(); bool getPersistent(); // is the persistent major, minor number check box checked QString getMajor(); QString getMinor(); QString getName(); QString getTag(); public: LvCreateDialogBase(const VolGroup *const vg, const long long maxFsSize, const bool extend, const bool snap, const bool thin, const bool thinpool, QString name = QString(""), QString pool = QString(""), // name = origin for snap or lvname for extend QWidget *parent = nullptr); virtual ~LvCreateDialogBase() {} }; #endif kvpm-0.9.10/kvpm/partbase.cpp0000644000175000017500000003470512770324126016301 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "partbase.h" #include "dualselectorbox.h" #include "fsreduce.h" #include "pedexceptions.h" #include "partitiongraphic.h" #include "physvol.h" #include "processprogress.h" #include "progressbox.h" #include "storagepartition.h" #include "volgroup.h" #include #include #include #include #include #include #include #include // NOTE: the cylinder_alignment flag is set to 0 for all devices in StorageDevice // Don't set it here because freespace "partitions" rearrange themselves for some // reason if it gets set after the partitions are probed. PartitionDialogBase::PartitionDialogBase(StoragePartition *const partition, QWidget *parent) : KDialog(parent), m_old_storage_part(partition) { m_is_new = (partition->isFreespace() || partition->isEmptyExtended()); if (m_is_new) { const unsigned ped_type = m_old_storage_part->getPedType(); const bool logical_freespace = (ped_type & PED_PARTITION_FREESPACE) && (ped_type & PED_PARTITION_LOGICAL); const int count = ped_disk_get_primary_partition_count(m_old_storage_part->getPedPartition()->disk); const int max_count = ped_disk_get_max_primary_partition_count(m_old_storage_part->getPedPartition()->disk); if (count >= max_count && (!(logical_freespace || m_old_storage_part->isExtended()))) { KMessageBox::sorry(0, i18n("This disk already has %1 primary partitions, the maximum", count)); m_bailout = true; } else if (m_old_storage_part->isExtended() && (!m_old_storage_part->isEmptyExtended())) { KMessageBox::sorry(0, i18n("This should not happen. Try selecting the freespace and not the partiton itself")); m_bailout = true; } else { m_bailout = false; } } else { if (m_old_storage_part->isMounted()) { KMessageBox::sorry(0, i18n("Mounted partitions cannot be changed")); m_bailout = true; } else { m_bailout = false; } } if (!m_bailout) { m_existing_part = m_old_storage_part->getPedPartition(); m_current_size = m_existing_part->geom.length; m_current_start = m_existing_part->geom.start; PedDisk *const disk = m_existing_part->disk; m_sector_size = disk->dev->sector_size; const PedSector ONE_MIB = 0x100000 / getSectorSize(); if (m_is_new) { m_min_shrink_size = 1; if (!setMaxFreespace(m_max_start, m_max_end)) { KMessageBox::sorry(0, i18n("No free space found")); m_bailout = true; } if (getMaxSize() < (3 * ONE_MIB)) { KMessageBox::sorry(0, i18n("Not enough free space for a new partition")); m_bailout = true; } } else { const PedSector current_end = (getCurrentSize() + getCurrentStart()) - 1; char *const ped_path = ped_partition_get_path(m_existing_part); m_path = QString(ped_path).trimmed(); free(ped_path); m_min_shrink_size = setMinSize(); setMaxPart(m_max_start, m_max_end); if (((getCurrentSize() - getMinSize()) < ONE_MIB) && ((getMaxEnd() - current_end) < ONE_MIB) && ((getCurrentStart() - getMaxStart()) < ONE_MIB)) { KMessageBox::sorry(0, i18n("Not enough free space to move or extend this partition and it can not be shrunk")); m_bailout = true; } } } if (!m_bailout) { buildDialog(); } } void PartitionDialogBase::buildDialog() { const PedSector max_size = 1 + (m_max_end - m_max_start); const PedSector existing_offset = m_existing_part->geom.start - m_max_start; const PedSector max_offset = max_size - m_min_shrink_size; m_display_graphic = new PartitionGraphic(m_sector_size * max_size, m_is_new); setButtons(KDialog::Ok | KDialog::Cancel | KDialog::Reset); setCaption(i18n("Move or resize a partition")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); m_layout = new QVBoxLayout(); QLabel *label = new QLabel(); if (m_is_new) label->setText(i18n("Create A New Partition")); else label->setText(i18n("Resize Or Move A Partition")); label->setAlignment(Qt::AlignCenter); m_layout->addSpacing(5); m_layout->addWidget(label); m_layout->addSpacing(5); label = new QLabel(i18n("Device: %1", m_old_storage_part->getName())); label->setAlignment(Qt::AlignHCenter); m_layout->addWidget(label); m_layout->addSpacing(5); m_layout->addWidget(m_display_graphic); if (m_old_storage_part->isPhysicalVolume()) { if (m_old_storage_part->getPhysicalVolume()->isActive()) { m_dual_selector = new DualSelectorBox(m_sector_size, max_size, m_min_shrink_size, max_size - existing_offset, m_existing_part->geom.length, existing_offset, existing_offset, existing_offset); } else { m_dual_selector = new DualSelectorBox(m_sector_size, max_size, m_min_shrink_size, max_size, m_existing_part->geom.length, 0, max_offset, existing_offset); } } else { m_dual_selector = new DualSelectorBox(m_sector_size, max_size, m_min_shrink_size, max_size, m_existing_part->geom.length, 0, max_offset, existing_offset); } m_layout->addWidget(m_dual_selector); dialog_body->setLayout(m_layout); m_dual_selector->resetSelectors(); connect(m_dual_selector, SIGNAL(changed()), this, SIGNAL(changed())); connect(this, SIGNAL(resetClicked()), m_dual_selector, SLOT(resetSelectors())); setDefaultButton(KDialog::Ok); } void PartitionDialogBase::updateGraphicAndLabels() { const long long current = m_dual_selector->getNewSize() * m_sector_size; const long long offset = m_dual_selector->getNewOffset() * m_sector_size; if (m_is_new) { m_display_graphic->update(current, offset); } else { const long long change = m_sector_size * (m_dual_selector->getNewSize() - m_existing_part->geom.length); const long long move = m_sector_size * (m_dual_selector->getNewOffset() - (m_existing_part->geom.start - m_max_start)); m_display_graphic->update(current, offset, move, change); } } bool PartitionDialogBase::setMaxFreespace(PedSector &start, PedSector &end) { PedPartition *const real_free = getPedPartition(); const PedSector sector = real_free->geom.start; PedDisk *const disk = ped_disk_duplicate(real_free->disk); PedDevice *const device = disk->dev; PedPartition *free_space = ped_disk_get_partition_by_sector(disk, sector); // If this is an empty extended partition and not freespace inside // one then look for the freespace. // Also, ped_disk_get_partition_by_sector(disk, sector) may return a metadata // area instead of free space. if (m_old_storage_part->isExtended()) { while (!((free_space->type & PED_PARTITION_FREESPACE) && (free_space->type & PED_PARTITION_LOGICAL))) { free_space = ped_disk_next_partition(disk, free_space); if (!free_space) { qDebug() << "Extended partition with no freespace found!"; return false; // bail out } } } start = free_space->geom.start; end = free_space->geom.length + start - 1; PedPartition *part; if (free_space->type & PED_PARTITION_LOGICAL) part = ped_partition_new(disk, PED_PARTITION_LOGICAL, NULL, start, end); else part = ped_partition_new(disk, PED_PARTITION_NORMAL, NULL, start, end); start = part->geom.start; PedSector length = part->geom.length; const PedSector ONE_MIB = 0x100000 / m_sector_size; // sectors per megabyte PedAlignment *const start_align = ped_alignment_new(0, ONE_MIB); PedAlignment *const end_align = ped_alignment_new(-1, ONE_MIB); PedGeometry *const start_range = ped_geometry_new(device, start, length); PedGeometry *const end_range = ped_geometry_new(device, start, length); PedConstraint *constraint = ped_constraint_new(start_align, end_align, start_range, end_range, 1, length); ped_disk_add_partition(disk, part, constraint); PedGeometry *max_geometry = ped_disk_get_max_partition_geometry(disk, part, constraint); start = max_geometry->start; end = max_geometry->length + max_geometry->start - 1; ped_disk_remove_partition(disk, part); ped_partition_destroy(part); ped_disk_destroy(disk); ped_constraint_destroy(constraint); ped_geometry_destroy(max_geometry); return true; } void PartitionDialogBase::setMaxPart(PedSector &start, PedSector &end) { PedPartition *const real_part = getPedPartition(); const PedSector sector = real_part->geom.start; PedDisk *const disk = ped_disk_duplicate(real_part->disk); PedDevice *const device = disk->dev; PedPartition *const part = ped_disk_get_partition_by_sector(disk, sector); const PedSector ONE_MIB = 0x100000 / m_sector_size; PedSector const old_start = part->geom.start; PedSector const old_end = part->geom.length + old_start - 1; PedConstraint *constraint = ped_constraint_any(device); PedGeometry *max_geometry = ped_disk_get_max_partition_geometry(disk, part, constraint); start = max_geometry->start; end = max_geometry->length + max_geometry->start - 1; ped_constraint_destroy(constraint); PedAlignment *const start_align = ped_alignment_new(0, ONE_MIB); PedAlignment *const end_align = ped_alignment_new(-1, ONE_MIB); PedGeometry *const start_range = ped_geometry_new(device, start, max_geometry->length); PedGeometry *const end_range = ped_geometry_new(device, start, max_geometry->length); constraint = ped_constraint_new(start_align, end_align, start_range, end_range, 1, max_geometry->length); ped_geometry_destroy(max_geometry); max_geometry = ped_disk_get_max_partition_geometry(disk, part, constraint); start = max_geometry->start; end = max_geometry->length + max_geometry->start - 1; // Don't return a size smaller than partition already is if (start > old_start) start = old_start; if (end < old_end) end = old_end; ped_disk_destroy(disk); ped_constraint_destroy(constraint); ped_geometry_destroy(max_geometry); } PedSector PartitionDialogBase::getNewOffset() { return m_dual_selector->getNewOffset(); } PedSector PartitionDialogBase::getNewSize() { return m_dual_selector->getNewSize(); } PedSector PartitionDialogBase::getSectorSize() { return m_sector_size; } PedSector PartitionDialogBase::getMaxSize() { return 1 + (m_max_end - m_max_start); } PedSector PartitionDialogBase::getMinSize() { return m_min_shrink_size; } PedSector PartitionDialogBase::getMaxStart() { return m_max_start; } PedSector PartitionDialogBase::getMaxEnd() { return m_max_end; } PedSector PartitionDialogBase::getCurrentStart() { return m_current_start; } PedSector PartitionDialogBase::getCurrentSize() { return m_current_size; } PedSector PartitionDialogBase::setMinSize() { PedSector min; const QString fs = m_old_storage_part->getFilesystem(); if (m_old_storage_part->isPhysicalVolume()) { PhysVol *const pv = m_old_storage_part->getPhysicalVolume(); const long mda_count = pv->getMdaCount(); const long long extent_size = pv->getVg()->getExtentSize(); const long long mda_extents = (pv->getMdaSize() / extent_size) + 1; min = 1 + (mda_extents * mda_count) + pv->getLastUsedExtent(); min *= extent_size; min /= m_sector_size; } else if (m_is_new) { min = 1; } else { if (fs_can_reduce(fs)) min = get_min_fs_size(m_path, fs) / m_sector_size; else min = m_existing_part->geom.length; } const PedSector TWO_MIB = 0x200000 / m_sector_size; if (min == 0) // 0 means we can't shrink it min = m_existing_part->geom.length; else if (min <= TWO_MIB) { // Don't allow shrinking or new partitions below 2 MiB if (m_existing_part->geom.length > TWO_MIB) min = TWO_MIB; else min = m_existing_part->geom.length; } return min; } PedPartition *PartitionDialogBase::getPedPartition() { return m_existing_part; } bool PartitionDialogBase::isValid() { return m_dual_selector->isValid(); } QString PartitionDialogBase::getPath() { return m_path; } void PartitionDialogBase::insertWidget(QWidget *widget) { m_layout->insertWidget(6, widget); } /* The following function waits for udev to acknowledge the partion changes before exiting It also sets what getCurrentSize() and getCurrentStart() return to the new current values */ bool PartitionDialogBase::pedCommitAndWait(PedDisk *disk) { QStringList args; qApp->setOverrideCursor(Qt::WaitCursor); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); m_current_size = m_existing_part->geom.length; m_current_start = m_existing_part->geom.start; if (!ped_disk_commit(disk)) { qApp->restoreOverrideCursor(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); return false; } else { args << "udevadm" << "settle"; ProcessProgress wait_settle(args); qApp->restoreOverrideCursor(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); return true; } } bool PartitionDialogBase::hasInitialErrors() { return m_bailout; } kvpm-0.9.10/kvpm/fsprobe.cpp0000644000175000017500000000343212770324126016131 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "fsprobe.h" #include #include #define BLKID_EMPTY_CACHE "/dev/null" QString fsprobe_getfstype2(const QString devicePath) { static blkid_cache blkid2; const QByteArray path = devicePath.toLocal8Bit(); blkid2 = NULL; if (blkid_get_cache(&blkid2, BLKID_EMPTY_CACHE) < 0) { return QString(); } else { char *tag = blkid_get_tag_value(blkid2, "TYPE", path.data()); QString type(tag); free(tag); blkid_put_cache(blkid2); return type; } } QString fsprobe_getfsuuid(const QString devicePath) { static blkid_cache blkid2; const QByteArray path = devicePath.toLocal8Bit(); blkid2 = NULL; if (blkid_get_cache(&blkid2, BLKID_EMPTY_CACHE) < 0) { return QString(); } else { char *tag = blkid_get_tag_value(blkid2, "UUID", path.data()); QString uuid(tag); free(tag); blkid_put_cache(blkid2); return uuid; } } QString fsprobe_getfslabel(const QString devicePath) { static blkid_cache blkid2; const QByteArray path = devicePath.toLocal8Bit(); blkid2 = NULL; if (blkid_get_cache(&blkid2, BLKID_EMPTY_CACHE) < 0) { return QString(); } else { char *tag = blkid_get_tag_value(blkid2, "LABEL", path.data()); QString label(tag); free(tag); blkid_put_cache(blkid2); return label; } } kvpm-0.9.10/kvpm/pvchange.cpp0000644000175000017500000001300712770324126016263 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvchange.h" #include #include #include #include #include #include #include #include #include "physvol.h" #include "processprogress.h" #include "volgroup.h" PVChangeDialog::PVChangeDialog(PhysVol *const physicalVolume, QWidget *parent) : KvpmDialog(parent), m_pv(physicalVolume) { setCaption(i18n("Change Physical Volume Attributes")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout; dialog_body->setLayout(layout); QString pv_name = m_pv->getMapperName(); QLabel *const label = new QLabel(i18n("Changing volume: %1", pv_name)); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); layout->addSpacing(5); QGroupBox *const attrib_box = new QGroupBox(i18n("Attributes")); QVBoxLayout *const attrib_box_layout = new QVBoxLayout; attrib_box->setLayout(attrib_box_layout); layout->addWidget(attrib_box); m_allocation_box = new QCheckBox(i18n("Allow allocation of extents")); if (m_pv->isAllocatable()) m_allocation_box->setChecked(true); attrib_box_layout->addWidget(m_allocation_box); m_mda_box = new QCheckBox(i18n("Use metadata areas on this volume")); if (m_pv->getMdaUsed()) m_mda_box->setChecked(true); if (m_pv->getVg()->getMdaUsed() == m_pv->getMdaUsed()) // pv has only usable mda's in vg m_mda_box->setEnabled(false); attrib_box_layout->addWidget(m_mda_box); m_uuid_box = new QCheckBox(i18n("Generate new UUID for this volume")); m_uuid_box->setChecked(false); if (m_pv->getVg()->isActive()) m_uuid_box->setEnabled(false); attrib_box_layout->addWidget(m_uuid_box); m_tags_group = new QGroupBox(i18n("Change tags")); m_tags_group->setCheckable(true); m_tags_group->setChecked(false); layout->addWidget(m_tags_group); QHBoxLayout *const add_tag_layout = new QHBoxLayout(); QHBoxLayout *const del_tag_layout = new QHBoxLayout(); QVBoxLayout *const tag_group_layout = new QVBoxLayout(); tag_group_layout->addLayout(add_tag_layout); tag_group_layout->addLayout(del_tag_layout); m_tags_group->setLayout(tag_group_layout); QLabel *const add_tag_label = new QLabel(i18n("Add new tag:")); add_tag_layout->addWidget(add_tag_label); m_tag_edit = new QLineEdit(); add_tag_label->setBuddy(m_tag_edit); QRegExp rx("[0-9a-zA-Z_\\.+-]*"); QRegExpValidator *tag_validator = new QRegExpValidator(rx, m_tag_edit); m_tag_edit->setValidator(tag_validator); add_tag_layout->addWidget(m_tag_edit); QLabel *const del_tag_label = new QLabel(i18n("Remove tag:")); del_tag_layout->addWidget(del_tag_label); m_deltag_combo = new QComboBox(); del_tag_label->setBuddy(m_deltag_combo); m_deltag_combo->setEditable(false); const QStringList tags = m_pv->getTags(); for (int x = 0; x < tags.size(); x++){ m_deltag_combo->addItem(tags[x]); } m_deltag_combo->insertItem(0, QString("")); m_deltag_combo->setCurrentIndex(0); del_tag_layout->addWidget(m_deltag_combo); del_tag_layout->addStretch(); layout->addWidget(m_tags_group); enableButtonOk(false); connect(m_allocation_box, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_mda_box, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_uuid_box, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_tags_group, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_deltag_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(resetOkButton())); connect(m_tag_edit, SIGNAL(textChanged(QString)), this, SLOT(resetOkButton())); } void PVChangeDialog::resetOkButton() { enableButtonOk(false); if (m_allocation_box->isChecked() != m_pv->isAllocatable()) enableButtonOk(true); if (m_mda_box->isChecked() != m_pv->getMdaUsed()) enableButtonOk(true); if (m_uuid_box->isChecked()) enableButtonOk(true); if ((m_deltag_combo->currentIndex() || (!m_tag_edit->text().isEmpty())) && m_tags_group->isChecked()) enableButtonOk(true); } QStringList PVChangeDialog::arguments() { QStringList args = QStringList() << "pvchange"; if (m_allocation_box->isChecked() && !m_pv->isAllocatable()) args << "--allocatable" << "y"; else if (!m_allocation_box->isChecked() && m_pv->isAllocatable()) args << "--allocatable" << "n"; if (m_mda_box->isChecked() && !m_pv->getMdaUsed()) args << "--metadataignore" << "n"; else if (!m_mda_box->isChecked() && m_pv->getMdaUsed()) args << "--metadataignore" << "y"; if (m_uuid_box->isChecked()) args << "--uuid"; if (m_tags_group->isChecked()) { if (m_deltag_combo->currentIndex()) args << "--deltag" << m_deltag_combo->currentText(); if (!m_tag_edit->text().isEmpty()) args << "--addtag" << m_tag_edit->text(); } args << m_pv->getMapperName(); return args; } void PVChangeDialog::commit() { hide(); ProcessProgress move(arguments()); return; } kvpm-0.9.10/kvpm/partitiongraphic.cpp0000644000175000017500000001217712770324126020046 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "partitiongraphic.h" #include #include #include #include #include // Two classes are set defined. Graphic body is privately used by PartitionGraphic // PartitionGraphic exists only to put a nice frame around the body widget. class GraphicBody : public QWidget { public: GraphicBody(QWidget *parent = NULL); long long m_preceding; long long m_following; long long m_size; void paintEvent(QPaintEvent *); }; GraphicBody::GraphicBody(QWidget *parent) : QWidget(parent) { setMinimumWidth(250); setMinimumHeight(30); m_preceding = 0; m_following = 0; m_size = 1; } void GraphicBody::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setPen(Qt::blue); const long double total = m_preceding + m_following + m_size; double offset = 0; double length = (m_preceding / total) * width(); QRectF preceding_rectangle(offset, 0.0, length, (double)height()); offset += length; length = (m_size / total) * width(); if (length < 1.0) // always show at least a sliver length = 1.0; QRectF partition_rectangle(offset, 0.0, length, (double)height()); offset += length; length = (m_following / total) * width(); QRectF following_rectangle(offset, 0.0, length, (double)height()); QBrush free_brush(Qt::green, Qt::SolidPattern); QBrush partition_brush(Qt::blue, Qt::SolidPattern); painter.fillRect(preceding_rectangle, free_brush); painter.fillRect(following_rectangle, free_brush); painter.fillRect(partition_rectangle, partition_brush); } PartitionGraphic::PartitionGraphic(long long space, bool isNewPart, QWidget *parent) : QGroupBox(parent), m_total_space(space) { if (m_total_space < 0) m_total_space = 0; KConfigSkeleton skeleton; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", m_use_si_units, false); QVBoxLayout *const layout = new QVBoxLayout(); QFrame *const graphic_frame = new QFrame(); QVBoxLayout *const graphic_layout = new QVBoxLayout(); graphic_frame->setFrameStyle(QFrame::Sunken | QFrame::Panel); graphic_frame->setLineWidth(2); graphic_layout->setSpacing(0); graphic_layout->setMargin(0); graphic_frame->setLayout(graphic_layout); m_body = new GraphicBody(); graphic_layout->addWidget(m_body); layout->addWidget(graphic_frame); QHBoxLayout *const lower_layout = new QHBoxLayout(); QVBoxLayout *const info_layout = new QVBoxLayout(); m_preceding_label = new QLabel(); m_following_label = new QLabel(); m_change_label = new QLabel(); m_move_label = new QLabel(); info_layout->addSpacing(5); info_layout->addWidget(m_preceding_label); info_layout->addWidget(m_following_label); info_layout->addSpacing(5); info_layout->addWidget(m_change_label); info_layout->addWidget(m_move_label); if (isNewPart) { m_change_label->hide(); m_move_label->hide(); } info_layout->addSpacing(5); lower_layout->addLayout(info_layout); lower_layout->addStretch(); layout->addLayout(lower_layout); setLayout(layout); } void PartitionGraphic::update(long long size, long long offset, long long move, long long change) { if (size < 0) size = 0; if (offset < 0) offset = 0; long long following = m_total_space - (size + offset); if (following < 0) following = 0; m_body->m_preceding = offset; m_body->m_following = following; m_body->m_size = size; m_body->repaint(); KFormat::BinaryUnitDialect dialect; if (m_use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; if (change < 0) m_change_label->setText(i18n("Reduce size: -%1", KFormat().formatByteSize(qAbs(change), 1, dialect))); else if (change == 0) m_change_label->setText(i18n("Size: no change")); else m_change_label->setText(i18n("Extend size: %1", KFormat().formatByteSize(change, 1, dialect))); if (move < 0) m_move_label->setText(i18n("Move (left): -%1", KFormat().formatByteSize(qAbs(move), 1, dialect))); else if (move == 0) m_move_label->setText(i18n("Move: no change")); else m_move_label->setText(i18n("Move (right): %1", KFormat().formatByteSize(move, 1, dialect))); m_preceding_label->setText(i18n("Preceding free space: %1", KFormat().formatByteSize(offset, 1, dialect))); m_following_label->setText(i18n("Following free space: %1", KFormat().formatByteSize(following, 1, dialect))); } void PartitionGraphic::update(long long size, long long offset) { update(size, offset, 0, 0); } kvpm-0.9.10/kvpm/kvpmdialog.cpp0000644000175000017500000000161412770324126016626 0ustar benscottbenscott/* * * * Copyright (C) 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "kvpmdialog.h" KvpmDialog::KvpmDialog(QWidget *parent) : KDialog(parent) { connect(this, SIGNAL(okClicked()), this, SLOT(commit())); connect(this, SIGNAL(yesClicked()), this, SLOT(commit())); } KvpmDialog::~KvpmDialog() { } int KvpmDialog::run() { int result = QDialog::Rejected; if (m_allow_run) result = exec(); return result; } void KvpmDialog::preventExec() { m_allow_run = false; setResult(QDialog::Rejected); } bool KvpmDialog::willExec() { return m_allow_run; } kvpm-0.9.10/kvpm/topwindow.cpp0000644000175000017500000004077512770324126016536 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2014 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "topwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kvpmconfigdialog.h" #include "devicetab.h" #include "executablefinder.h" #include "masterlist.h" #include "maintabwidget.h" #include "processprogress.h" #include "progressbox.h" #include "pvmove.h" #include "vgactions.h" #include "volgroup.h" #include "volumegrouptab.h" ProgressBox* TopWindow::m_progress_box = nullptr; // Static initializing TopWindow::TopWindow(MasterList *const masterList, ExecutableFinder *const executableFinder, QWidget *parent) : KMainWindow(parent), m_master_list(masterList), m_executable_finder(executableFinder) { m_progress_box = new ProgressBox(); // make sure this stays *before* master_list->rescan() gets called! menuBar()->addMenu(buildFileMenu()); menuBar()->addMenu(buildGroupsMenu()); menuBar()->addMenu(buildToolsMenu()); menuBar()->addMenu(buildSettingsMenu()); menuBar()->addMenu(buildHelpMenu()); m_tab_widget = new MainTabWidget(this); setCentralWidget(m_tab_widget); m_device_tab = new DeviceTab(); m_tab_widget->appendDeviceTab(m_device_tab, i18n("Storage Devices")); menuBar()->setCornerWidget(m_progress_box); connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanUp())); reRun(); // reRun also does the initial run } void TopWindow::reRun() { qApp->setOverrideCursor(Qt::WaitCursor); m_master_list->rescan(); // loads the list with new data updateTabs(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); qApp->restoreOverrideCursor(); } ProgressBox* TopWindow::getProgressBox() { return m_progress_box; } void TopWindow::updateTabs() { VolumeGroupTab *tab = nullptr; bool vg_exists = false; disconnect(m_tab_widget, SIGNAL(currentIndexChanged(int)), this, SLOT(setVgMenu(int))); m_device_tab->rescan(MasterList::getStorageDevices()); QList groups = MasterList::getVolGroups(); // if there is a tab for a deleted vg then delete the tab for (int x = 1; x < m_tab_widget->getCount(); ++x) { vg_exists = false; for (auto vg : groups) { if (m_tab_widget->getUnmungedText(x) == vg->getName()) vg_exists = true; } if (!vg_exists) m_tab_widget->deleteTab(x); } // if there is a new vg and no tab then create one for (auto vg : groups) { vg_exists = false; for (int x = 1; x < m_tab_widget->getCount(); ++x) { if (m_tab_widget->getUnmungedText(x) == vg->getName()) { vg_exists = true; if (vg->isPartial() || vg->openFailed()) m_tab_widget->setIcon(x, QIcon::fromTheme(QStringLiteral("dialog-warning"))); else m_tab_widget->setIcon(x, QIcon()); } } if (!vg_exists) { tab = new VolumeGroupTab(vg); if (vg->isPartial() || vg->openFailed()) m_tab_widget->appendVolumeGroupTab(tab, QIcon::fromTheme(QStringLiteral("dialog-warning")), vg->getName()); else m_tab_widget->appendVolumeGroupTab(tab, QIcon(), vg->getName()); } } for (int x = 0; x < (m_tab_widget->getCount() - 1); ++x) m_tab_widget->getVolumeGroupTab(x)->rescan(); setVgMenu(m_tab_widget->getCurrentIndex()); connect(m_tab_widget, SIGNAL(currentIndexChanged(int)), this, SLOT(setVgMenu(int))); } void TopWindow::callToolsDialog(QAction *action) { if (action->objectName() == "restartpvmove") { if (restart_pvmove()) reRun(); } else if (action->objectName() == "stoppvmove") { if (stop_pvmove()) reRun(); } else if (action->objectName() == "systemreload") { reRun(); } } void TopWindow::configKvpm() { KvpmConfigDialog *const dialog = new KvpmConfigDialog(this, "settings", new KConfigSkeleton(), m_executable_finder); connect(dialog, SIGNAL(applyClicked()), this, SLOT(updateTabs())); dialog->exec(); disconnect(dialog, SIGNAL(applyClicked()), this, SLOT(updateTabs())); dialog->deleteLater(); updateTabs(); } void TopWindow::cleanUp() { delete m_master_list; // This calls lvm_quit() on destruct } void TopWindow::closeEvent(QCloseEvent *) { qApp->quit(); } void TopWindow::showVolumeGroupInfo(bool show) { KConfigSkeleton skeleton; bool show_vg_info; skeleton.setCurrentGroup("General"); skeleton.addItemBool("show_vg_info", show_vg_info, true); show_vg_info = show; skeleton.save(); updateTabs(); } void TopWindow::showVolumeGroupBar(bool show) { KConfigSkeleton skeleton; bool show_lv_bar; skeleton.setCurrentGroup("General"); skeleton.addItemBool("show_lv_bar", show_lv_bar, true); show_lv_bar = show; skeleton.save(); updateTabs(); } void TopWindow::showDeviceBar(bool show) { KConfigSkeleton skeleton; bool show_device_bar; skeleton.setCurrentGroup("General"); skeleton.addItemBool("show_device_barchart", show_device_bar, true); show_device_bar = show; skeleton.save(); updateTabs(); } void TopWindow::showToolbars(bool show) { KConfigSkeleton skeleton; bool show_toolbars; skeleton.setCurrentGroup("General"); skeleton.addItemBool("show_toolbars", show_toolbars, true); show_toolbars = show; skeleton.save(); updateTabs(); } void TopWindow::useSiUnits(bool use) { KConfigSkeleton skeleton; bool use_si_units; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", use_si_units, false); use_si_units = use; skeleton.save(); updateTabs(); } void TopWindow::setToolbarIconSize(QAction *action) { KConfigSkeleton skeleton; QString icon_size; skeleton.setCurrentGroup("General"); skeleton.addItemString("toolbar_icon_size", icon_size, "mediumicons"); icon_size = action->objectName(); skeleton.save(); updateTabs(); } void TopWindow::setToolbarIconText(QAction *action) { KConfigSkeleton skeleton; QString icon_text; skeleton.setCurrentGroup("General"); skeleton.addItemString("toolbar_icon_text", icon_text, "iconsonly"); icon_text = action->objectName(); skeleton.save(); updateTabs(); } QMenu *TopWindow::buildToolbarSizeMenu() { QMenu *const menu = new QMenu(i18n("Toolbar icon size")); KToggleAction *const small_action = new KToggleAction(i18n("Small icons (16x16)"), this); KToggleAction *const medium_action = new KToggleAction(i18n("Medium icons (22x22)"), this); KToggleAction *const large_action = new KToggleAction(i18n("Large icons (32x32)"), this); KToggleAction *const huge_action = new KToggleAction(i18n("Huge icons (48x48)"), this); small_action->setObjectName("smallicons"); medium_action->setObjectName("mediumicons"); large_action->setObjectName("largeicons"); huge_action->setObjectName("hugeicons"); QActionGroup *const size_group = new QActionGroup(this); size_group->addAction(small_action); size_group->addAction(medium_action); size_group->addAction(large_action); size_group->addAction(huge_action); connect(size_group, SIGNAL(triggered(QAction *)), this, SLOT(setToolbarIconSize(QAction *))); menu->addAction(small_action); menu->addAction(medium_action); menu->addAction(large_action); menu->addAction(huge_action); KConfigSkeleton skeleton; QString icon_size; skeleton.setCurrentGroup("General"); skeleton.addItemString("toolbar_icon_size", icon_size, "mediumicons"); if (icon_size == "smallicons") small_action->setChecked(true); else if (icon_size == "mediumicons") medium_action->setChecked(true); else if (icon_size == "largeicons") large_action->setChecked(true); else if (icon_size == "hugeicons") huge_action->setChecked(true); return menu; } QMenu *TopWindow::buildToolbarTextMenu() { QMenu *const menu = new QMenu(i18n("Toolbar text placement")); KToggleAction *const icons_only_action = new KToggleAction(i18n("Icons only"), this); KToggleAction *const text_only_action = new KToggleAction(i18n("Text only"), this); KToggleAction *const text_beside_action = new KToggleAction(i18n("Text beside icons"), this); KToggleAction *const text_under_action = new KToggleAction(i18n("Text under icons"), this); QActionGroup *const icon_group = new QActionGroup(this); icons_only_action->setObjectName("iconsonly"); text_only_action->setObjectName("textonly"); text_beside_action->setObjectName("textbesideicons"); text_under_action->setObjectName("textundericons"); icon_group->addAction(icons_only_action); icon_group->addAction(text_only_action); icon_group->addAction(text_beside_action); icon_group->addAction(text_under_action); connect(icon_group, SIGNAL(triggered(QAction *)), this, SLOT(setToolbarIconText(QAction *))); menu->addAction(icons_only_action); menu->addAction(text_only_action); menu->addAction(text_beside_action); menu->addAction(text_under_action); KConfigSkeleton skeleton; QString icon_text; skeleton.setCurrentGroup("General"); skeleton.addItemString("toolbar_icon_text", icon_text, "iconsonly"); if (icon_text == "iconsonly") icons_only_action->setChecked(true); else if (icon_text == "textonly") text_only_action->setChecked(true); else if (icon_text == "textbesideicons") text_beside_action->setChecked(true); else if (icon_text == "textundericons") text_under_action->setChecked(true); return menu; } QMenu *TopWindow::buildSettingsMenu() { QMenu *const menu = new QMenu(i18n("Settings")); KToggleAction *const show_vg_info_action = new KToggleAction(i18n("Show Volume Group Information"), this); KToggleAction *const show_lv_bar_action = new KToggleAction(i18n("Show Volume Group Bar Chart"), this); KToggleAction *const show_device_bar_action = new KToggleAction(i18n("Show Device Bar Chart"), this); KToggleAction *const use_si_units_action = new KToggleAction(i18n("Use Metric SI Units"), this); KToggleAction *const show_toolbars_action = new KToggleAction(i18n("Show Toolbars"), this); QAction *const config_kvpm_action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure kvpm..."), this); menu->addAction(show_vg_info_action); menu->addAction(show_lv_bar_action); menu->addAction(show_device_bar_action); menu->addAction(use_si_units_action); menu->addSeparator(); menu->addAction(show_toolbars_action); menu->addMenu(buildToolbarTextMenu()); menu->addMenu(buildToolbarSizeMenu()); menu->addSeparator(); menu->addAction(config_kvpm_action); KConfigSkeleton skeleton; bool show_vg_info, show_lv_bar, use_si_units, show_toolbars, show_device_bar; skeleton.setCurrentGroup("General"); skeleton.addItemBool("show_vg_info", show_vg_info, true); skeleton.addItemBool("show_lv_bar", show_lv_bar, true); skeleton.addItemBool("show_device_barchart", show_device_bar, true); skeleton.addItemBool("use_si_units", use_si_units, false); skeleton.addItemBool("show_toolbars", show_toolbars, true); // This must be *before* the following connect() statements show_vg_info_action->setChecked(show_vg_info); show_lv_bar_action->setChecked(show_lv_bar); show_device_bar_action->setChecked(show_device_bar); use_si_units_action->setChecked(use_si_units); show_toolbars_action->setChecked(show_toolbars); connect(show_vg_info_action, SIGNAL(toggled(bool)), this, SLOT(showVolumeGroupInfo(bool))); connect(show_lv_bar_action, SIGNAL(toggled(bool)), this, SLOT(showVolumeGroupBar(bool))); connect(show_device_bar_action, SIGNAL(toggled(bool)), this, SLOT(showDeviceBar(bool))); connect(use_si_units_action, SIGNAL(toggled(bool)), this, SLOT(useSiUnits(bool))); connect(show_toolbars_action, SIGNAL(toggled(bool)), this, SLOT(showToolbars(bool))); connect(config_kvpm_action, SIGNAL(triggered()), this, SLOT(configKvpm())); return menu; } QMenu *TopWindow::buildToolsMenu() { QMenu *const menu = new QMenu(i18n("Tools")); QAction *const rescan_action = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Rescan System"), this); QAction *const restart_pvmove_action = new QAction(QIcon::fromTheme(QStringLiteral("system-reboot")), i18n("Restart interrupted pvmove"), this); QAction *const stop_pvmove_action = new QAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Abort pvmove"), this); rescan_action->setObjectName("systemreload"); restart_pvmove_action->setObjectName("restartpvmove"); stop_pvmove_action->setObjectName("stoppvmove"); menu->addAction(rescan_action); menu->addSeparator(); menu->addAction(restart_pvmove_action); menu->addAction(stop_pvmove_action); QActionGroup *const actions = new QActionGroup(this); actions->addAction(rescan_action); actions->addAction(restart_pvmove_action); actions->addAction(stop_pvmove_action); connect(actions, SIGNAL(triggered(QAction *)), this, SLOT(callToolsDialog(QAction *))); return menu; } QMenu *TopWindow::buildHelpMenu() { static KAboutData about_data(QStringLiteral("kvpm"), xi18nc("@title", "kvpm"), QStringLiteral("0.9.10"), xi18nc("@title", "Linux volume and partition manager for KDE. " "This program is still under development, " "bug reports and any comments are welcomed. " "Licensed under GNU General Public License v3.0"), KAboutLicense::GPL_V3, xi18nc("@info:credit", "(c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott"), QString(), QStringLiteral("http://sourceforge.net/projects/kvpm/"), QStringLiteral("benscott@nwlink.com")); about_data.setHomepage(QStringLiteral("http://sourceforge.net/projects/kvpm/")); about_data.addCredit(xi18nc("@info:credit", "Mark James"), xi18nc("@info:credit", "Additional icons taken from the Silk icon set by " "Mark James under the Creative Commons Attribution 3.0 License."), QString(), QStringLiteral("http://www.famfamfam.com/lab/icons/silk/")); KHelpMenu *const help_menu = new KHelpMenu(this, about_data); return help_menu->menu(); } QMenu *TopWindow::buildFileMenu() { QMenu *const menu = new QMenu(i18n("File")); QAction *const quit_action = KStandardAction::quit(qApp, SLOT(quit()), menu); menu->addAction(quit_action); return menu; } void TopWindow::setVgMenu(int index) { m_vg_actions->setVg(MasterList::getVgByName(m_tab_widget->getUnmungedText(index))); } QMenu *TopWindow::buildGroupsMenu() { m_vg_actions = new VGActions(this); QMenu *const menu = new QMenu(i18n("Volume Groups")); menu->addAction(m_vg_actions->action("vgcreate")); menu->addAction(m_vg_actions->action("vgremove")); menu->addAction(m_vg_actions->action("vgrename")); menu->addSeparator(); menu->addAction(m_vg_actions->action("vgremovemissing")); menu->addAction(m_vg_actions->action("vgextend")); menu->addAction(m_vg_actions->action("vgreduce")); menu->addAction(m_vg_actions->action("vgsplit")); menu->addAction(m_vg_actions->action("vgmerge")); menu->addSeparator(); menu->addAction(m_vg_actions->action("vgimport")); menu->addAction(m_vg_actions->action("vgexport")); menu->addAction(m_vg_actions->action("vgchange")); return menu; } kvpm-0.9.10/kvpm/masterlist.cpp0000644000175000017500000001517612770324126016670 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2014 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "masterlist.h" #include "externalraid.h" #include "fsprobe.h" #include "logvol.h" #include "mountentry.h" #include "mounttables.h" #include "pedexceptions.h" #include "pedthread.h" #include "physvol.h" #include "processprogress.h" #include "progressbox.h" #include "storagedevice.h" #include "topwindow.h" #include "volgroup.h" #include #include #include #include // These are static being variables initialized here QList MasterList::m_volume_groups = QList(); QList MasterList::m_storage_devices = QList(); lvm_t MasterList::m_lvm = NULL; int MasterList::m_LvmVersionMajor = 0; int MasterList::m_LvmVersionMinor = 0; int MasterList::m_LvmVersionPatchLevel = 0; int MasterList::m_LvmVersionApi = 0; MasterList::MasterList() : QObject() { m_lvm = lvm_init(NULL); QStringList version = QString(lvm_library_get_version()).split('.'); m_LvmVersionMajor = version[0].toInt(); m_LvmVersionMinor = version[1].toInt(); version = version[2].split(QRegExp("[()]")); m_LvmVersionPatchLevel = version[0].toInt(); m_LvmVersionApi = version[1].toInt(); ped_exception_set_handler(my_handler); m_mount_tables = new MountTables(); } MasterList::~MasterList() { if (m_lvm) lvm_quit(m_lvm); for(auto vg : m_volume_groups) delete vg; for(auto dev : m_storage_devices) delete dev; delete m_mount_tables; } void MasterList::rescan() { ProgressBox *progress_box = TopWindow::getProgressBox(); m_mount_tables->loadData(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); progress_box->setRange(0, 3); progress_box->setValue(0); progress_box->setText("Scan system"); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); // The lvm and PED scan can both be slow so we // run the PED system scan in a background thread QSemaphore semaphore(1); PedThread ped_thread(&semaphore); ped_thread.start(); lvm_scan(m_lvm); lvm_config_reload(m_lvm); semaphore.acquire(1); // wait for thread to finish qApp->processEvents(QEventLoop::ExcludeUserInputEvents); progress_box->setValue(1); progress_box->setText("Scan vgs"); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); scanVolumeGroups(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); progress_box->setValue(2); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); progress_box->setText("Scan devs"); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); scanStorageDevices(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); progress_box->reset(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); return; } void MasterList::scanVolumeGroups() { dm_list *vgnames; lvm_str_list *strl; vgnames = lvm_list_vg_names(m_lvm); dm_list_iterate_items(strl, vgnames) { // rescan() existing VolGroup, don't create a new one bool existing_vg = false; for (int x = 0; x < m_volume_groups.size(); x++) { if (QString(strl->str).trimmed() == m_volume_groups[x]->getName()) { existing_vg = true; m_volume_groups[x]->rescan(m_lvm); } } if (!existing_vg) m_volume_groups.append(new VolGroup(m_lvm, strl->str, m_mount_tables)); } for (int x = m_volume_groups.size() - 1; x >= 0; x--) { // delete VolGroup if the vg is gone bool deleted_vg = true; dm_list_iterate_items(strl, vgnames) { if (QString(strl->str).trimmed() == m_volume_groups[x]->getName()) deleted_vg = false; } if (deleted_vg) delete m_volume_groups.takeAt(x); } } void MasterList::scanStorageDevices() { QList physical_volumes; for (auto vg : m_volume_groups) physical_volumes << vg->getPhysicalVolumes(); for (auto sd : m_storage_devices) delete sd; m_storage_devices.clear(); PedDevice *dev = nullptr; QStringList dmblock, dmraid; QStringList mdblock, mdraid; dmraid_get_devices(dmblock, dmraid); mdraid_get_devices(mdblock, mdraid); while ((dev = ped_device_get_next(dev))) { const QString path(dev->path); if (!path.startsWith("/dev/mapper") && !(mdblock.contains(path) && mdraid.contains(path))) m_storage_devices.append(new StorageDevice(dev, physical_volumes, m_mount_tables, dmblock, dmraid, mdblock, mdraid)); else if (dmraid.contains(path)) m_storage_devices.prepend(new StorageDevice(dev, physical_volumes, m_mount_tables, dmblock, dmraid, mdblock, mdraid)); } } int MasterList::getVgCount() { return m_volume_groups.size(); } QList MasterList::getVolGroups() { return m_volume_groups; } QList MasterList::getStorageDevices() { return m_storage_devices; } lvm_t MasterList::getLvm() { return m_lvm; } VolGroup* MasterList::getVgByName(QString name) { name = name.trimmed(); for (int x = 0; x < m_volume_groups.size(); x++) { if (name == m_volume_groups[x]->getName()) return m_volume_groups[x]; } return NULL; } QStringList MasterList::getVgNames() { QStringList names; for (int x = 0; x < m_volume_groups.size(); x++) names << m_volume_groups[x]->getName(); return names; } int MasterList::getLvmVersionMajor() { return m_LvmVersionMajor; } int MasterList::getLvmVersionMinor() { return m_LvmVersionMinor; } int MasterList::getLvmVersionPatchLevel() { return m_LvmVersionPatchLevel; } int MasterList::getLvmVersionApi() { return m_LvmVersionApi; } bool MasterList::isLvmVersionEqualOrGreater(QString test_version) { QStringList version = test_version.split('.'); if ( m_LvmVersionMajor > version[0].toInt() ) return true; else if ( m_LvmVersionMajor < version[0].toInt() ) return false; if ( m_LvmVersionMinor > version[1].toInt() ) return true; else if ( m_LvmVersionMinor < version[1].toInt()) return false; version = version[2].split(QRegExp("[()]")); if ( m_LvmVersionPatchLevel >= version[0].toInt()) return true; else return false; } kvpm-0.9.10/kvpm/fsck.cpp0000644000175000017500000000430112770324126015413 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "fsck.h" #include #include #include #include "logvol.h" #include "processprogress.h" #include "storagepartition.h" bool fsck(const QString path) { QStringList arguments, output; arguments << "fsck" << "-fp" << path; ProcessProgress fsck_fs(arguments); output = fsck_fs.programOutput(); if (fsck_fs.exitCode() > 1) // 0 means no errors, 1 means minor errors fixed return false; else return true; } bool manual_fsck(LogVol *const logicalVolume) { const QString path = logicalVolume->getMapperPath(); const QString warning = i18n("Run 'fsck -fp' to check the filesystem on volume %1?", path); if (KMessageBox::warningYesNo(NULL, warning, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { return fsck(path); } else { return false; } } bool manual_fsck(StoragePartition *const partition) { const QString path = partition->getName(); const QString warning = i18n("Run 'fsck -fp' to check the filesystem on partition %1?", path); if (KMessageBox::warningYesNo(NULL, warning, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { return fsck(path); } else { return false; } } kvpm-0.9.10/kvpm/vgchange.cpp0000644000175000017500000003351012770324126016253 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgchange.h" #include "allocationpolicy.h" #include "logvol.h" #include "misc.h" #include "processprogress.h" #include "volgroup.h" #include #include #include #include #include #include #include #include #include #include VGChangeDialog::VGChangeDialog(VolGroup *const group, QWidget *parent) : KvpmDialog(parent), m_vg(group) { setCaption(i18n("Change Volume Group Attributes")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); QLabel *const name_label = new QLabel(i18n("Change volume group: %1", m_vg->getName())); name_label->setAlignment(Qt::AlignCenter); layout->addWidget(name_label); layout->addSpacing(10); QGroupBox *const misc_box = new QGroupBox(); QVBoxLayout *const misc_layout = new QVBoxLayout(); misc_box->setLayout(misc_layout); layout->addWidget(misc_box); m_resize = new QCheckBox(i18n("Allow physical volume addition and removal")); m_resize->setChecked(m_vg->isResizable()); misc_layout->addWidget(m_resize); m_refresh = new QCheckBox(i18n("Refresh metadata")); misc_layout->addWidget(m_refresh); m_uuid = new QCheckBox(i18n("Generate new UUID fo group")); if (m_vg->isActive()) m_uuid->setEnabled(false); misc_layout->addWidget(m_uuid); misc_layout->addSpacing(5); misc_layout->addWidget(new KSeparator(Qt::Horizontal)); misc_layout->addSpacing(5); m_clustered = new QCheckBox(i18n("Enable cluster support")); m_clustered->setChecked(m_vg->isClustered()); misc_layout->addWidget(m_clustered); QLabel *const cluster_label = new QLabel(); cluster_label->setText(i18n("Caution: do not enable cluster support unless you have a working cluster running")); cluster_label->setWordWrap(true); misc_layout->addWidget(cluster_label); misc_layout->addSpacing(5); misc_layout->addWidget(new KSeparator(Qt::Horizontal)); misc_layout->addSpacing(5); AllocationPolicy const policy = m_vg->getPolicy(); m_policy_combo = new PolicyComboBox(policy); QHBoxLayout *const policy_layout = new QHBoxLayout(); policy_layout->addWidget(m_policy_combo); policy_layout->addStretch(); misc_layout->addLayout(policy_layout); m_extent_size_combo = new QComboBox(); m_extent_size_combo->insertItem(0, "1"); m_extent_size_combo->insertItem(1, "2"); m_extent_size_combo->insertItem(2, "4"); m_extent_size_combo->insertItem(3, "8"); m_extent_size_combo->insertItem(4, "16"); m_extent_size_combo->insertItem(5, "32"); m_extent_size_combo->insertItem(6, "64"); m_extent_size_combo->insertItem(7, "128"); m_extent_size_combo->insertItem(8, "256"); m_extent_size_combo->insertItem(9, "512"); m_extent_size_combo->setInsertPolicy(QComboBox::NoInsert); m_extent_size_combo->setCurrentIndex(2); m_extent_suffix_combo = new QComboBox(); m_extent_suffix_combo->insertItem(0, "KiB"); m_extent_suffix_combo->insertItem(1, "MiB"); m_extent_suffix_combo->insertItem(2, "GiB"); m_extent_suffix_combo->setInsertPolicy(QComboBox::NoInsert); m_extent_suffix_combo->setCurrentIndex(1); long long current_extent_size = m_vg->getExtentSize() / 1024; if (current_extent_size <= 512) { m_extent_suffix_combo->setCurrentIndex(0); } else if (((current_extent_size /= 1024)) <= 512) { m_extent_suffix_combo->setCurrentIndex(1); } else { m_extent_suffix_combo->setCurrentIndex(2); current_extent_size /= 1024; } for (int i = 0; i < 10; i++) { if (current_extent_size == m_extent_size_combo->itemText(i).toLongLong()) m_extent_size_combo->setCurrentIndex(i); } QHBoxLayout *const extent_layout = new QHBoxLayout(); extent_layout->addWidget(new QLabel(i18n("Extent size:"))); extent_layout->addWidget(m_extent_size_combo); extent_layout->addWidget(m_extent_suffix_combo); extent_layout->addStretch(); misc_layout->addLayout(extent_layout); connect(m_extent_suffix_combo, SIGNAL(activated(int)), this, SLOT(limitExtentSize(int))); QHBoxLayout *const middle_layout = new QHBoxLayout(); layout->addLayout(middle_layout); m_available_box = new QGroupBox("Change group availability"); QVBoxLayout *const available_layout = new QVBoxLayout(); m_available_yes = new QRadioButton(i18n("Make all logical volumes available (active)")); m_available_no = new QRadioButton(i18n("Make all logical volumes unavailable (inactive)")); m_available_yes->setChecked(true); available_layout->addWidget(m_available_yes); available_layout->addWidget(m_available_no); m_available_box->setLayout(available_layout); m_available_box->setCheckable(true); m_available_box->setChecked(false); middle_layout->addWidget(m_available_box); m_polling_box = new QGroupBox("Change group polling"); QVBoxLayout *const polling_layout = new QVBoxLayout(); m_polling_yes = new QRadioButton(i18n("Start polling")); m_polling_no = new QRadioButton(i18n("Stop polling")); m_polling_yes->setChecked(true); polling_layout->addWidget(m_polling_yes); polling_layout->addWidget(m_polling_no); m_polling_box->setLayout(polling_layout); m_polling_box->setCheckable(true); m_polling_box->setChecked(false); middle_layout->addWidget(m_polling_box); LvList lv_list = m_vg->getLogicalVolumes(); for (int x = lv_list.size() - 1; x >= 0 ; x--) { // replace snap containers with first level children if (lv_list[x]->isSnapContainer()) { lv_list.append(lv_list[x]->getChildren()); lv_list.removeAt(x); } } for (int x = lv_list.size() - 1; x >= 0 ; x--) { if (lv_list[x]->isMounted()) { m_available_yes->setChecked(true); m_available_no->setEnabled(false); break; } } // We don't want the limit set to less than the number already in existence! int lv_count = m_vg->getLvCount(); if (lv_count <= 0) lv_count = 1; m_limit_box = new QGroupBox(i18n("Change maximum limit for number of volumes")); QVBoxLayout *const limit_groupbox_layout = new QVBoxLayout(); m_limit_box->setLayout(limit_groupbox_layout); QLabel *const lv_limit_label = new QLabel(i18n("Logical volumes")); QLabel *const pv_limit_label = new QLabel(i18n("Physical volumes")); QString lv_current_max; QString pv_current_max; if (m_vg->getLvMax() > 0) lv_current_max = QString("%1").arg(m_vg->getLvMax()); else lv_current_max = QString("unlimited"); if (m_vg->getPvMax() > 0) pv_current_max = QString("%1").arg(m_vg->getPvMax()); else pv_current_max = QString("unlimited"); QLabel *const lv_min_label = new QLabel(i18n("Currently: %1 Minimum: %2", lv_current_max, m_vg->getLvCount())); QLabel *const pv_min_label = new QLabel(i18n("Currently: %1 Minimum: %2", pv_current_max, m_vg->getPvCount())); QHBoxLayout *const lv_layout = new QHBoxLayout(); QHBoxLayout *const pv_layout = new QHBoxLayout(); m_max_pvs_spin = new QSpinBox(); m_max_lvs_spin = new QSpinBox(); lv_limit_label->setBuddy(m_max_lvs_spin); pv_limit_label->setBuddy(m_max_pvs_spin); if (m_vg->getFormat() == "lvm1") { m_max_lvs_spin->setEnabled(true); m_max_lvs_spin->setRange(lv_count, 255); } else { m_limit_box->setCheckable(true); m_limit_box->setChecked(false); m_limit_box->setEnabled(true); m_max_lvs_spin->setSpecialValueText(i18n("unlimited")); m_max_lvs_spin->setRange(0, 32767); // does anyone need more than 32 thousand? } m_max_lvs_spin->setValue(m_vg->getLvMax()); lv_layout->addWidget(lv_limit_label); lv_layout->addWidget(m_max_lvs_spin); lv_layout->addWidget(lv_min_label); lv_layout->addStretch(); pv_layout->addWidget(pv_limit_label); pv_layout->addWidget(m_max_pvs_spin); pv_layout->addWidget(pv_min_label); pv_layout->addStretch(); limit_groupbox_layout->addLayout(lv_layout); limit_groupbox_layout->addLayout(pv_layout); // We don't want the limit set to less than the number already in existence! int pv_count = m_vg->getPvCount(); if (pv_count <= 0) pv_count = 1; if (m_vg->getFormat() == "lvm1") { m_max_pvs_spin->setEnabled(true); m_max_pvs_spin->setRange(pv_count, 255); } else { m_max_pvs_spin->setSpecialValueText(i18n("unlimited")); m_max_pvs_spin->setRange(0, 32767); // does anyone need more than 32 thousand? } m_max_pvs_spin->setValue(m_vg->getPvMax()); layout->addWidget(m_limit_box); connect(m_available_yes, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_available_no, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_polling_yes, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_polling_no, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_available_box, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_polling_box, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_resize, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_clustered, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_refresh, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_uuid, SIGNAL(clicked()), this, SLOT(resetOkButton())); connect(m_limit_box , SIGNAL(toggled(bool)), this, SLOT(resetOkButton())); connect(m_max_lvs_spin, SIGNAL(valueChanged(int)), this, SLOT(resetOkButton())); connect(m_max_pvs_spin, SIGNAL(valueChanged(int)), this, SLOT(resetOkButton())); connect(m_extent_size_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(resetOkButton())); connect(m_extent_suffix_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(resetOkButton())); connect(m_policy_combo, SIGNAL(policyChanged(AllocationPolicy)), this, SLOT(resetOkButton())); enableButtonOk(false); } void VGChangeDialog::commit() { ProcessProgress vgchange(arguments()); } QStringList VGChangeDialog::arguments() { QStringList args = QStringList() << "vgchange"; if (m_vg->getPolicy() != m_policy_combo->getPolicy()) args << "--alloc" << policyToString(m_policy_combo->getPolicy()); if (m_available_box->isChecked()) { if (m_available_yes->isChecked()) args << "--available" << "y"; else args << "--available" << "n"; } if (m_limit_box->isChecked()) { if (m_max_lvs_spin->value() != m_vg->getLvMax()) args << "--logicalvolume" << QString("%1").arg(m_max_lvs_spin->value()); if (m_max_pvs_spin->value() != m_vg->getPvMax()) args << "--maxphysicalvolumes" << QString("%1").arg(m_max_pvs_spin->value()); } if (m_resize->isChecked() != m_vg->isResizable()) { if (m_resize->isChecked()) args << "--resizeable" << "y"; else args << "--resizeable" << "n"; } if (m_clustered->isChecked() != m_vg->isClustered()) { if (m_clustered->isChecked()) args << "--clustered" << "y"; else args << "--clustered" << "n"; } if (m_refresh->isChecked()) args << "--refresh"; if (m_uuid->isChecked()) args << "--uuid"; long long new_extent_size = m_extent_size_combo->currentText().toLongLong(); new_extent_size *= 1024; if (m_extent_suffix_combo->currentIndex() > 0) new_extent_size *= 1024; if (m_extent_suffix_combo->currentIndex() > 1) new_extent_size *= 1024; if (new_extent_size != m_vg->getExtentSize()) { args << "--physicalextentsize" << QString("%1b").arg(new_extent_size); } if (m_polling_box->isChecked()) { if (m_polling_yes->isChecked()) args << "--poll" << "y"; else args << "--poll" << "n"; } args << m_vg->getName(); return args; } void VGChangeDialog::resetOkButton() { if (arguments().size() > 2) enableButtonOk(true); else enableButtonOk(false); if (m_limit_box->isChecked()) { if ((m_max_lvs_spin->value() < m_vg->getLvCount()) && (m_max_lvs_spin->value() != 0)) enableButtonOk(false); if ((m_max_pvs_spin->value() < m_vg->getPvCount()) && (m_max_pvs_spin->value() != 0)) enableButtonOk(false); } } void VGChangeDialog::limitExtentSize(int index) { int extent_index; if (index > 1) { // Gigabytes selected as suffix, more than 2Gib forbidden if (m_extent_size_combo->currentIndex() > 2) m_extent_size_combo->setCurrentIndex(0); m_extent_size_combo->setMaxCount(2); } else { extent_index = m_extent_size_combo->currentIndex(); m_extent_size_combo->setMaxCount(10); m_extent_size_combo->setInsertPolicy(QComboBox::InsertAtBottom); m_extent_size_combo->insertItem(3, "4"); m_extent_size_combo->insertItem(3, "8"); m_extent_size_combo->insertItem(4, "16"); m_extent_size_combo->insertItem(5, "32"); m_extent_size_combo->insertItem(6, "64"); m_extent_size_combo->insertItem(7, "128"); m_extent_size_combo->insertItem(8, "256"); m_extent_size_combo->insertItem(9, "512"); m_extent_size_combo->setInsertPolicy(QComboBox::NoInsert); m_extent_size_combo->setCurrentIndex(extent_index); } } kvpm-0.9.10/kvpm/devicemenu.h0000644000175000017500000000116012770324126016256 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef DEVICEMENU_H #define DEVICEMENU_H #include class DeviceActions; class DeviceMenu : public QMenu { Q_OBJECT public: explicit DeviceMenu(DeviceActions *const devacts, QWidget *parent); }; #endif kvpm-0.9.10/kvpm/main.cpp0000644000175000017500000000525012770324126015415 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "executablefinder.h" #include "lvmconfig.h" #include "masterlist.h" #include "topwindow.h" #include #include #include #include #include #include #include #include class VolGroup; class PhysVol; class LogVol; TopWindow *g_top_window; int main(int argc, char **argv) { QApplication app(argc, argv); KLocalizedString::setApplicationDomain("kvpm"); KAboutData about_data(QStringLiteral("kvpm"), xi18nc("@title", "kvpm"), QStringLiteral("0.9.10"), xi18nc("@title", "The Linux volume and partition manager for KDE.\n" "Licensed under the GPL v3.0\n \n" "Additional icons taken from the Silk icon set by Mark James.\n" "http://www.famfamfam.com/lab/icons/silk/\n" "Licensed under the under the Creative Commons Attribution 3.0 License."), KAboutLicense::GPL_V3, xi18nc("@info:credit", "Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott")); QCommandLineParser parser; parser.setApplicationDescription(about_data.shortDescription()); parser.addHelpOption(); parser.addVersionOption(); about_data.setupCommandLine(&parser); parser.process(app); about_data.processCommandLine(&parser); if (geteuid() != 0) { KMessageBox::sorry(0, i18n("This program must be run as root (uid = 0) "), i18n("Insufficient Privilege")); return 0; } QPixmap splashImage(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kvpm/images/splash.png")); QSplashScreen splash(splashImage); splash.show(); ExecutableFinder *executable_finder = new ExecutableFinder(); LvmConfig config; config.initialize(); MasterList *const master_list = new MasterList(); g_top_window = new TopWindow(master_list, executable_finder, nullptr); g_top_window->setAutoSaveSettings(); g_top_window->show(); splash.finish(g_top_window); return app.exec(); } kvpm-0.9.10/kvpm/lvreduce.h0000644000175000017500000000145112770324126015746 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVREDUCEDIALOG_H #define LVREDUCEDIALOG_H #include "kvpmdialog.h" class LogVol; class SizeSelectorBox; class LVReduceDialog : public KvpmDialog { Q_OBJECT LogVol *m_lv = nullptr; SizeSelectorBox *m_size_selector = nullptr; public: explicit LVReduceDialog(LogVol *const volume, QWidget *parent = nullptr); private slots: void commit(); void resetOkButton(); }; #endif kvpm-0.9.10/kvpm/dualselectorbox.h0000644000175000017500000000242112770324126017332 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef DUALSELECTORBOX_H #define DUALSELECTORBOX_H #include class SizeSelectorBox; class DualSelectorBox : public QWidget { Q_OBJECT SizeSelectorBox *m_size_selector; SizeSelectorBox *m_offset_selector; long long m_space; signals: void changed(); public: DualSelectorBox(const long long sectorSize, const long long totalSpace, const long long minSize, const long long maxSize, const long long initialSize, const long long minOffset, const long long maxOffset, const long long initialOffset, QWidget *parent = 0); DualSelectorBox(const long long sectorSize, const long long totalSpace, QWidget *parent = 0); long long getNewSize(); long long getNewOffset(); bool isValid(); public slots: void resetSelectors(); private slots: void offsetChanged(); void sizeChanged(); }; #endif kvpm-0.9.10/kvpm/fsblocksize.cpp0000644000175000017500000000212312770324126017003 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "fsblocksize.h" #include #include "processprogress.h" long get_fs_block_size(QString path) { QStringList arguments; QString block_string; arguments << "dumpe2fs" << "-h" << path; ProcessProgress blocksize_scan(arguments); const QStringList output = blocksize_scan.programOutput(); const QStringList temp_stringlist = output.filter("Block size", Qt::CaseInsensitive); if (temp_stringlist.size()) { block_string = temp_stringlist[0]; block_string = block_string.remove(0, block_string.indexOf(":") + 1); block_string = block_string.simplified(); } else return 0; return block_string.toLong(); } kvpm-0.9.10/kvpm/partchange.h0000644000175000017500000000214312770324126016250 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PARTCHANGESIZE_H #define PARTCHANGESIZE_H #include #include "partbase.h" class StoragePartition; class PartitionChangeDialog : public PartitionDialogBase { Q_OBJECT StoragePartition *m_old_storage_part; bool m_bailout; bool movefs(const PedSector from_start, const PedSector to_start, const PedSector length); bool shrinkPartition(); bool growPartition(); bool movePartition(); bool continueBackup(); bool continueResize(); bool continueBusy(); public: explicit PartitionChangeDialog(StoragePartition *const partition, QWidget *parent = NULL); bool bailout(); private slots: void commitPartition(); void validateChange(); }; #endif kvpm-0.9.10/kvpm/snapmerge.cpp0000644000175000017500000000246512770324126016457 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "snapmerge.h" #include "logvol.h" #include "processprogress.h" #include #include #include bool merge_snap(LogVol *const snapshot) { QStringList args; const QString warning = i18n("Merge snapshot: %1 with origin: %2?", snapshot->getName(), snapshot->getOrigin()); if (KMessageBox::warningYesNo(NULL, warning, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { args << "lvconvert" << "--merge" << "--background" << snapshot->getFullName(); ProcessProgress merge(args); return true; } else { return false; } } kvpm-0.9.10/kvpm/devicesizechartseg.cpp0000644000175000017500000001320412770324126020342 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "devicesizechartseg.h" #include "devicemenu.h" #include "storagepartition.h" #include #include #include #include DeviceChartSeg::DeviceChartSeg(QTreeWidgetItem *const storageItem, QWidget *parent) : QFrame(parent), m_item(storageItem) { QColor ext2_color, ext3_color, ext4_color, reiser_color, reiser4_color, msdos_color, jfs_color, xfs_color, none_color, btrfs_color, swap_color, hfs_color, physical_color, ntfs_color; KConfigSkeleton skeleton; skeleton.setCurrentGroup("FilesystemColors"); skeleton.addItemColor("ext2", ext2_color, Qt::blue); skeleton.addItemColor("ext3", ext3_color, Qt::darkBlue); skeleton.addItemColor("ext4", ext4_color, Qt::cyan); skeleton.addItemColor("btrfs", btrfs_color, Qt::yellow); skeleton.addItemColor("reiser", reiser_color, Qt::red); skeleton.addItemColor("reiser4", reiser4_color, Qt::darkRed); skeleton.addItemColor("msdos", msdos_color, Qt::darkYellow); skeleton.addItemColor("jfs", jfs_color, Qt::magenta); skeleton.addItemColor("xfs", xfs_color, Qt::darkGreen); skeleton.addItemColor("hfs", hfs_color, Qt::darkMagenta); skeleton.addItemColor("ntfs", ntfs_color, Qt::darkGray); skeleton.addItemColor("none", none_color, Qt::black); skeleton.addItemColor("swap", swap_color, Qt::lightGray); skeleton.addItemColor("physvol", physical_color, Qt::darkCyan); int type_combo_index; skeleton.setCurrentGroup("General"); skeleton.addItemInt("type_combo", type_combo_index, 0); QColor free_color; skeleton.setCurrentGroup("VolumeTypeColors"); skeleton.addItemColor("free", free_color, Qt::green); QColor primary_color, logical_color, extended_color; skeleton.setCurrentGroup("PartitionTypeColors"); skeleton.addItemColor("primary", primary_color, Qt::cyan); skeleton.addItemColor("logical", logical_color, Qt::blue); skeleton.addItemColor("extended", extended_color, Qt::darkGreen); QPalette colorset; QString use = (m_item->data(4, Qt::DisplayRole)).toString(); m_partition = nullptr; if ((m_item->data(0, Qt::UserRole)).canConvert()) { m_partition = (StoragePartition *)((m_item->data(0, Qt::UserRole)).value()); setFrameStyle(QFrame::Sunken | QFrame::Panel); setLineWidth(2); if (m_partition->isExtended()) { colorset.setColor(QPalette::Window, extended_color); } else if (m_partition->isLogicalFreespace()) { colorset.setColor(QPalette::Window, extended_color); } else if (m_partition->isFreespace()) { colorset.setColor(QPalette::Window, free_color); } else if (type_combo_index == 1) { if (use == "ext2") colorset.setColor(QPalette::Window, ext2_color); else if (use == "ext3") colorset.setColor(QPalette::Window, ext3_color); else if (use == "ext4") colorset.setColor(QPalette::Window, ext4_color); else if (use == "reiserfs") colorset.setColor(QPalette::Window, reiser_color); else if (use == "reiser4") colorset.setColor(QPalette::Window, reiser4_color); else if (use == "hfs") colorset.setColor(QPalette::Window, hfs_color); else if (use == "ntfs") colorset.setColor(QPalette::Window, ntfs_color); else if (use == "vfat") colorset.setColor(QPalette::Window, msdos_color); else if (use == "jfs") colorset.setColor(QPalette::Window, jfs_color); else if (use == "xfs") colorset.setColor(QPalette::Window, xfs_color); else if (use == "btrfs") colorset.setColor(QPalette::Window, btrfs_color); else if (use == "swap") colorset.setColor(QPalette::Window, swap_color); else if (use == "freespace") colorset.setColor(QPalette::Window, free_color); else if (use == "PV") colorset.setColor(QPalette::Window, physical_color); else colorset.setColor(QPalette::Window, none_color); } else { if (m_partition->isNormal()) colorset.setColor(QPalette::Window, primary_color); else if (m_partition->isLogical()) colorset.setColor(QPalette::Window, logical_color); } } else { // whole device, not a partition setFrameStyle(QFrame::Sunken | QFrame::Panel); setLineWidth(2); if (use == "PV") colorset.setColor(QPalette::Window, physical_color); else colorset.setColor(QPalette::Window, none_color); } setToolTip(i18n("Device: %1", m_item->data(0, Qt::DisplayRole).toString())); setPalette(colorset); setAutoFillBackground(true); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(popupContextMenu())); } void DeviceChartSeg::popupContextMenu() { emit deviceMenuRequested(m_item); } kvpm-0.9.10/kvpm/topwindow.h0000644000175000017500000000354112770324126016171 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef TOPWINDOW_H #define TOPWINDOW_H #include class QMenu; class DeviceTab; class ExecutableFinder; class MainTabWidget; class MasterList; class ProgressBox; class TopWindow; class VGActions; extern TopWindow *g_top_window; class TopWindow : public KMainWindow { Q_OBJECT MainTabWidget *m_tab_widget = nullptr; // The current tab widget we are using VGActions *m_vg_actions = nullptr; DeviceTab *m_device_tab = nullptr; MasterList *m_master_list; ExecutableFinder *m_executable_finder; static ProgressBox *m_progress_box; void closeEvent(QCloseEvent *); QMenu *buildFileMenu(); QMenu *buildGroupsMenu(); QMenu *buildHelpMenu(); QMenu *buildSettingsMenu(); QMenu *buildToolbarSizeMenu(); QMenu *buildToolbarTextMenu(); QMenu *buildToolsMenu(); public: TopWindow(MasterList *const masterList, ExecutableFinder *const executableFinder, QWidget *parent = nullptr); static ProgressBox *getProgressBox(); void reRun(); public slots: void updateTabs(); private slots: void cleanUp(); void setVgMenu(int index); void showVolumeGroupInfo(bool show); void showVolumeGroupBar(bool show); void showDeviceBar(bool show); void showToolbars(bool show); void useSiUnits(bool use); void setToolbarIconSize(QAction *action); void setToolbarIconText(QAction *action); void callToolsDialog(QAction *action); void configKvpm(); }; #endif kvpm-0.9.10/kvpm/lvproperties.cpp0000644000175000017500000003402612770324126017232 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvproperties.h" #include #include #include #include #include #include #include "logvol.h" #include "misc.h" #include "mountentry.h" #include "mounttables.h" #include "volgroup.h" /* if segment = -1 we have a multi segement logical volume but we are not focused on any one segment. Therefor stripes and stripe size have no meaning */ LVProperties::LVProperties(LogVol *const volume, const int segment, QWidget *parent) : QWidget(parent), m_lv(volume) { QVBoxLayout *const layout = new QVBoxLayout(); layout->setSpacing(2); layout->setMargin(2); KConfigSkeleton skeleton; bool show_mount, show_fsuuid, show_fslabel, show_uuid; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", m_use_si_units, false); skeleton.setCurrentGroup("LogicalVolumeProperties"); skeleton.addItemBool("lp_mount", show_mount, true); skeleton.addItemBool("lp_fsuuid", show_fsuuid, false); skeleton.addItemBool("lp_fslabel", show_fslabel, false); skeleton.addItemBool("lp_uuid", show_uuid, false); layout->addWidget(generalFrame(segment)); if (!m_lv->isLvmMirrorLeg() && !m_lv->isVirtual() && !m_lv->isRaidImage() && !m_lv->isPvmove() && !m_lv->isLvmMirrorLog() && !m_lv->isMetadata() && !m_lv->isSnapContainer() && !m_lv->isThinPool() && !m_lv->isThinPoolData() && ((m_lv->getSegmentCount() == 1) || (segment == -1))) { if (show_mount) layout->addWidget(mountPointsFrame()); layout->addWidget(physicalVolumesFrame(segment)); if (show_fsuuid || show_fslabel) layout->addWidget(fsFrame(show_fsuuid, show_fslabel)); } else layout->addWidget(physicalVolumesFrame(segment)); if (show_uuid && !m_lv->isSnapContainer() && ((m_lv->getSegmentCount() == 1) || (segment == -1))) layout->addWidget(uuidFrame()); layout->addStretch(); setLayout(layout); } QFrame *LVProperties::mountPointsFrame() { QLabel *label; QFrame *const frame = new QFrame(); QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); auto entries = m_lv->getMountEntries(); if (entries.size() > 1) { label = new QLabel(i18n("Mount Points")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); } else { label = new QLabel(i18n("Mount Point")) ; label->setAlignment(Qt::AlignCenter); layout->addWidget(label); } if (entries.isEmpty()) { label = new QLabel(i18n("not mounted")) ; label->setAlignment(Qt::AlignLeft); layout->addWidget(label); } else { for (int x = 0; x < entries.size(); ++x) { const int pos = entries[x]->getMountPosition(); const QString mp = entries[x]->getMountPoint(); if (pos > 0) { label = new QLabel(QString("%1 <%2>").arg(mp).arg(pos)); label->setToolTip(QString("%1 <%2>").arg(mp).arg(pos)); layout->addWidget(label); } else { label = new QLabel(mp); label->setToolTip(mp); layout->addWidget(label); } } } frame->setLayout(layout); return frame; } QFrame *LVProperties::uuidFrame() { QStringList uuid; QFrame *const frame = new QFrame(); QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); QLabel *label = new QLabel(i18n("Logical Volume UUID")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); uuid = splitUuid(m_lv->getUuid()); label = new QLabel(uuid[0]); label->setToolTip(m_lv->getUuid()); layout->addWidget(label); label = new QLabel(uuid[1]); label->setToolTip(m_lv->getUuid()); layout->addWidget(label); frame->setLayout(layout); return frame; } QFrame *LVProperties::fsFrame(const bool showFsUuid, const bool showFsLabel) { QStringList uuid; QFrame *const frame = new QFrame(); QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); QLabel *label; if (showFsLabel) { label = new QLabel(i18n("Filesystem Label")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); label = new QLabel(m_lv->getFilesystemLabel()); label->setToolTip(m_lv->getFilesystemLabel()); label->setWordWrap(true); layout->addWidget(label); } if (showFsUuid) { label = new QLabel(i18n("Filesystem UUID")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); uuid = splitUuid(m_lv->getFilesystemUuid()); label = new QLabel(uuid[0]); label->setToolTip(m_lv->getFilesystemUuid()); layout->addWidget(label); label = new QLabel(uuid[1]); label->setToolTip(m_lv->getFilesystemUuid()); layout->addWidget(label); } frame->setLayout(layout); return frame; } QFrame *LVProperties::generalFrame(int segment) { const long long extent_size = m_lv->getVg()->getExtentSize(); const int segment_count = m_lv->getSegmentCount(); long long extents, total_size, total_extents; int stripes, stripe_size; QFrame *const frame = new QFrame(); QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); KFormat::BinaryUnitDialect dialect; if (m_use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; QString policy = policyToLocalString(m_lv->getPolicy()); if (m_lv->isLocked()) policy.append(i18n(", locked")); if (m_lv->isThinPool()) { total_extents = m_lv->getTotalSize() / extent_size; total_size = m_lv->getTotalSize(); layout->addWidget(new QLabel(i18n("Total Extents: %1", total_extents))); layout->addWidget(new QLabel(i18n("Total Size: %1", KFormat().formatByteSize(total_size, 1, dialect)))); if (m_lv->isWritable()) layout->addWidget(new QLabel(i18n("Access: r/w"))); else layout->addWidget(new QLabel(i18n("Access: r/o"))); layout->addWidget(new QLabel(i18n("Chunk Size: %1", KFormat().formatByteSize(m_lv->getChunkSize(0), 1, dialect)))); if (m_lv->willZero()) layout->addWidget(new QLabel(i18n("Zero new blocks: Yes"))); else layout->addWidget(new QLabel(i18n("Zero new blocks: No"))); layout->addWidget(new QLabel(i18n("Discards: %1", m_lv->getDiscards(0)))); layout->addWidget(new QLabel(i18n("Policy: %1", policy))); } else if (m_lv->isSnapContainer()) { total_extents = m_lv->getTotalSize() / extent_size; total_size = m_lv->getTotalSize(); layout->addWidget(new QLabel(i18n("Total Extents: %1", total_extents))); layout->addWidget(new QLabel(i18n("Total Size: %1", KFormat().formatByteSize(total_size, 1, dialect)))); } else if ((segment >= 0) && (segment_count > 1)) { extents = m_lv->getSegmentExtents(segment); stripes = m_lv->getSegmentStripes(segment); stripe_size = m_lv->getSegmentStripeSize(segment); layout->addWidget(new QLabel(i18n("Extents: %1", extents))); if (!m_lv->isLvmMirror() && !m_lv->isRaidImage()) { if (stripes != 1) layout->addWidget(new QLabel(i18n("Stripes: %1 of %2", stripes, KFormat().formatByteSize(stripe_size, 1, dialect)))); else layout->addWidget(new QLabel(i18n("Stripes: none"))); } } else if ((segment >= 0) && (segment_count == 1)) { extents = m_lv->getSegmentExtents(segment); total_size = m_lv->getTotalSize(); total_extents = total_size / extent_size; stripes = m_lv->getSegmentStripes(segment); stripe_size = m_lv->getSegmentStripeSize(segment); if (!(m_lv->isSnapContainer() || m_lv->isRaid() || m_lv->isLvmMirror()) || m_lv->isRaidMetadata()) layout->addWidget(new QLabel(i18n("Extents: %1", extents))); if ( !(m_lv->isRaidImage() || m_lv->isRaidMetadata()) ) { if (m_lv->isRaid() && m_lv->getRaidType() != 1) { layout->addWidget(new QLabel(i18n("Total extents: %1", total_extents))); layout->addWidget(new QLabel(i18n("Total size: %1", KFormat().formatByteSize(total_size, 1, dialect)))); layout->addWidget(new QLabel(i18n("Stripes: %1 of %2", stripes, KFormat().formatByteSize(stripe_size, 1, dialect)))); } else if (!m_lv->isThinVolume() && !m_lv->isLvmMirror() && !(m_lv->isRaid() && m_lv->getRaidType() == 1)) { if (stripes != 1) layout->addWidget(new QLabel(i18n("Stripes: %1 of %2", stripes, KFormat().formatByteSize(stripe_size, 1, dialect)))); else layout->addWidget(new QLabel(i18n("Stripes: none"))); } else if (!m_lv->isThinVolume() && (!m_lv->isLvmMirrorLog() || (m_lv->isLvmMirrorLog() && m_lv->isLvmMirror()))) { layout->addWidget(new QLabel(i18n("Total extents: %1", total_extents))); layout->addWidget(new QLabel(i18n("Total size: %1", KFormat().formatByteSize(total_size, 1, dialect)))); } if (!(m_lv->isLvmMirrorLeg() || m_lv->isLvmMirrorLog() || m_lv->isThinMetadata())) { if (!m_lv->isThinPoolData()) layout->addWidget(new QLabel(i18n("Filesystem: %1", m_lv->getFilesystem()))); if (m_lv->isWritable()) layout->addWidget(new QLabel(i18n("Access: r/w"))); else layout->addWidget(new QLabel(i18n("Access: r/o"))); } } if (m_lv->isThinVolume()) { if (m_lv->willZero()) layout->addWidget(new QLabel(i18n("Zero new blocks: Yes"))); else layout->addWidget(new QLabel(i18n("Zero new blocks: No"))); } else if ( !(m_lv->isRaidImage() || m_lv->isMetadata() || m_lv->isLvmMirrorLeg() || m_lv->isLvmMirrorLog()) ) { layout->addWidget(new QLabel(i18n("Policy: %1", policy))); } } else { layout->addWidget(new QLabel(i18n("Extents: %1", m_lv->getExtents()))); if (m_lv->isThinPoolData() || m_lv->isThinMetadata()) { if (m_lv->isWritable()) layout->addWidget(new QLabel(i18n("Access: r/w"))); else layout->addWidget(new QLabel(i18n("Access: r/o"))); layout->addWidget(new QLabel(i18n("Policy: %1", policy))); } else if (!(m_lv->isLvmMirrorLeg() || m_lv->isLvmMirrorLog() || m_lv->isRaidImage())) { layout->addWidget(new QLabel(i18n("Filesystem: %1", m_lv->getFilesystem()))); if (m_lv->isWritable()) layout->addWidget(new QLabel(i18n("Access: r/w"))); else layout->addWidget(new QLabel(i18n("Access: r/o"))); layout->addWidget(new QLabel(i18n("Policy: %1", policy))); } } if (m_lv->isCowSnap()) layout->addWidget(new QLabel(i18n("Origin: %1", m_lv->getOrigin()))); frame->setLayout(layout); return frame; } QFrame *LVProperties::physicalVolumesFrame(int segment) { QStringList pv_list; QFrame *const frame = new QFrame(); QVBoxLayout *const layout = new QVBoxLayout(); frame->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); frame->setLineWidth(2); QLabel *label = new QLabel(i18n("Physical Volumes")); if (m_lv->isThinVolume()) label->setText(i18n("Thin Pool")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); if (m_lv->isThinVolume() && m_lv->isSnapContainer()) { label = new QLabel(m_lv->getPoolName()); label->setToolTip(m_lv->getPoolName()); layout->addWidget(label); pv_list = m_lv->getPvNamesAllFlat(); if (!pv_list.isEmpty()) { label = new QLabel(i18n("Physical Volumes")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); for (int pv = 0; pv < pv_list.size(); pv++) { label = new QLabel(pv_list[pv]); label->setToolTip(pv_list[pv]); layout->addWidget(label); } } } else if (m_lv->isThinVolume()) { label = new QLabel(m_lv->getPoolName()); label->setToolTip(m_lv->getPoolName()); layout->addWidget(label); } else if ((m_lv->isThinPool() || m_lv->isMirror() || m_lv->isSnapContainer() || m_lv->isRaid()) && !m_lv->isPvmove()) { pv_list = m_lv->getPvNamesAllFlat(); for (int pv = 0; pv < pv_list.size(); pv++) { label = new QLabel(pv_list[pv]); label->setToolTip(pv_list[pv]); layout->addWidget(label); } } else if (segment > -1) { pv_list = m_lv->getPvNames(segment); for (int pv = 0; pv < pv_list.size(); pv++) { label = new QLabel(pv_list[pv]); label->setToolTip(pv_list[pv]); layout->addWidget(label); } } else { pv_list = m_lv->getPvNamesAll(); for (int pv = 0; pv < pv_list.size(); pv++) { label = new QLabel(pv_list[pv]); label->setToolTip(pv_list[pv]); layout->addWidget(label); } } frame->setLayout(layout); return frame; } kvpm-0.9.10/kvpm/partadd.h0000644000175000017500000000206412770324126015555 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PARTADD_H #define PARTADD_H #include #include "partbase.h" class QComboBox; class QGroupBox; class StoragePartition; class PartitionAddDialog : public PartitionDialogBase { Q_OBJECT StoragePartition *m_storage_partition; PedConstraint *m_ped_constraints; bool m_bailout; QComboBox *m_type_combo; void getMaximumPartition(PedSector &start, PedSector &end, PedSector §orSize); QGroupBox *buildTypeWidget(); public: explicit PartitionAddDialog(StoragePartition *const partition, QWidget *parent = NULL); bool bailout(); private slots: void commitPartition(); void validateChange(); }; #endif kvpm-0.9.10/kvpm/unmount.h0000644000175000017500000000201112770324126015633 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef UNMOUNT_H #define UNMOUNT_H #include "mounttables.h" #include "kvpmdialog.h" class LogVol; class NoMungeCheck; class StoragePartition; class UnmountDialog : public KvpmDialog { Q_OBJECT bool m_single; QString m_mp; QList m_check_list; // one check box for each mount point public: UnmountDialog(StoragePartition *const partition, QWidget *parent = NULL); UnmountDialog(LogVol *const volume, QWidget *parent = NULL); void buildDialog(QString const device, const MountList entries); bool bailout(); private slots: void resetOkButton(); void commit(); }; #endif kvpm-0.9.10/kvpm/mount.h0000644000175000017500000000417412770324126015304 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef MOUNT_H #define MOUNT_H #include "kvpmdialog.h" class QCheckBox; class QGroupBox; class QLineEdit; class QRadioButton; class QString; class LogVol; class StoragePartition; class MountDialog : public KvpmDialog { Q_OBJECT QString m_mount_point, // The desired mount point m_device_to_mount, // The complete device path m_filesystem_type; // ext3, reiserfs, vfat etc. QRadioButton *ext2_button, *ext3_button, *ext4_button, *reiserfs3_button, *btrfs_button, *reiserfs4_button, *xfs_button, *jfs_button, *vfat_button, *ntfs_button, *specify_button, *udf_button, *iso9660_button, *hfs_button, *atime_button, *noatime_button, *relatime_button, *data_journal_button, *data_ordered_button, *data_writeback_button; QCheckBox *sync_check, *dirsync_check, *rw_check, *suid_check, *dev_check, *exec_check, *mand_check, *acl_check, *user_xattr_check, *nodiratime_check; QLineEdit *m_mount_point_edit, *m_filesystem_edit, // User chosen filesystem type *m_fs_specific_edit; // Additional options such as acl and data=ordered QGroupBox *m_filesystem_journal_box; bool m_is_writable; QWidget* filesystemBox(); QWidget* mountPointBox(); QWidget* mainTab(); QWidget* optionsTab(); void buildDialog(); public: MountDialog(LogVol *const volume, QWidget *parent = nullptr); MountDialog(StoragePartition *const partition, QWidget *parent = nullptr); private slots: void selectMountPoint(bool); void commit(); void toggleOKButton(); void toggleAdditionalOptions(bool); }; #endif kvpm-0.9.10/kvpm/thincreate.h0000644000175000017500000000254412770324126016267 0ustar benscottbenscott/* * * * Copyright (C) 2012, 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef THINCREATE_H #define THINCREATE_H #include "lvcreatebase.h" #include class LogVol; class ThinCreateDialog : public LvCreateDialogBase { Q_OBJECT bool m_snapshot; // TRUE if a snapshot bool m_extend; // TRUE if extending a volume bool m_fs_can_extend; bool m_use_si_units; // TRUE Metric SI sizes = MB and GB, otherise use MiB, GiB etc. LogVol *m_lv; // origin for snap or lv to extend // set to NULL if creating a new logical volume LogVol *m_pool; // The thin pool we are using if creating a new volume, set to NULL otherwise. long long getLargestVolume(); bool hasInitialErrors(); QStringList args(); public: explicit ThinCreateDialog(LogVol *const pool, QWidget *parent = nullptr); ThinCreateDialog(LogVol *const volume, const bool snapshot, QWidget *parent = nullptr); private slots: void setMaxSize(); void resetOkButton(); void commit(); }; #endif kvpm-0.9.10/kvpm/vgtree.cpp0000644000175000017500000004677612770324126016007 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgtree.h" #include #include #include #include #include #include #include "lvactionsmenu.h" #include "logvol.h" #include "volgroup.h" VGTree::VGTree(VolGroup *const group) : QTreeWidget(), m_vg(group) { m_init = true; const QStringList headers = QStringList() << i18n("Volume") << i18n("type") << i18n("Size") << i18n("Remaining") << i18n("Filesystem") << i18n("Stripes") << i18n("Stripe size") << i18n("Data/Copy") << i18n("State") << i18n("Access") << i18n("Tags") << i18n("Mount points"); QTreeWidgetItem *item = new QTreeWidgetItem(static_cast(nullptr), headers); for (int column = 0; column < item->columnCount() ; column++) item->setTextAlignment(column, Qt::AlignCenter); item->setToolTip(0, i18n("Logical volume name")); item->setToolTip(1, i18n("Type of logical volume")); item->setToolTip(2, i18n("Total size of the logical volume")); item->setToolTip(3, i18n("Free space on logical volume")); item->setToolTip(4, i18n("Filesystem type on logical volume, if any")); item->setToolTip(5, i18n("Number of stripes if the volume is striped")); item->setToolTip(6, i18n("Size of stripes if the volume is striped")); item->setToolTip(7, i18n("Percentage of pvmove completed, of mirror synced or of snapshot used up")); item->setToolTip(8, i18n("Logical volume state")); item->setToolTip(9, i18n("Read and write or Read Only")); item->setToolTip(10, i18n("Optional tags for physical volume")); item->setToolTip(11, i18n("Filesystem mount points, if mounted")); setHeaderItem(item); sortByColumn(0, Qt::AscendingOrder); setupContextMenu(); } void VGTree::loadData() { LvList logical_volumes = m_vg->getLogicalVolumes(); LogVol *lv = nullptr; QTreeWidgetItem *new_item; disconnect(this, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(adjustColumnWidth(QTreeWidgetItem *))); disconnect(this, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(adjustColumnWidth(QTreeWidgetItem *))); setSortingEnabled(false); setViewConfig(); for (int x = 0; x < m_vg->getLvCount(); x++) { lv = logical_volumes[x]; new_item = nullptr; for (int y = topLevelItemCount() - 1; y >= 0; y--) { if (topLevelItem(y)->data(0, Qt::DisplayRole).toString() == lv->getName()) new_item = loadItem(lv, topLevelItem(y)); } if (new_item == nullptr) { new_item = new QTreeWidgetItem(static_cast(nullptr)); addTopLevelItem(new_item); loadItem(lv, new_item); } } for (int y = topLevelItemCount() - 1; y >= 0; y--) { // remove top level lv items of deleted lvs bool match = false; for (int x = 0; x < logical_volumes.size(); x++) { if (topLevelItem(y)->data(0, Qt::DisplayRole).toString() == logical_volumes[x]->getName()) match = true; } if (!match) { delete takeTopLevelItem(y); } } connect(this, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(adjustColumnWidth(QTreeWidgetItem *))); connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(adjustColumnWidth(QTreeWidgetItem *))); setSortingEnabled(true); if (currentItem() == nullptr && topLevelItemCount() > 0) { setCurrentItem(topLevelItem(0)); scrollToItem(currentItem(), QAbstractItemView::EnsureVisible); } else if (currentItem() != nullptr) { setCurrentItem(currentItem()); scrollToItem(currentItem(), QAbstractItemView::EnsureVisible); } for (int x = 0; x < 10; x++) resizeColumnToContents(x); m_init = false; emit currentItemChanged(currentItem(), currentItem()); return; } QTreeWidgetItem *VGTree::loadItem(LogVol *lv, QTreeWidgetItem *item) { const QString old_type = item->data(1, Qt::DisplayRole).toString(); // lv type before reload or "" if new item const QString lv_name = lv->getName(); const bool was_sc = old_type.contains("origin", Qt::CaseInsensitive); const bool is_sc = lv->isSnapContainer(); const int old_child_count = item->childCount(); bool was_expanded = false; KFormat::BinaryUnitDialect dialect; if (m_use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; LvList temp_kids; long long fs_remaining; // remaining space on fs -- if known int fs_percent; // percentage of space remaining /* UserRole definitions: setData(0, Qt::UserRole, lv->getName()); setData(1, Qt::UserRole, segment); // segment number setData(2, Qt::UserRole, lv->getUuid()); setData(3, Qt::UserRole, QString("segment")); // "" if not a segment item setData(4, Qt::UserRole, bool); // true if it is a snap container */ if (is_sc && !was_sc) { was_expanded = item->isExpanded(); qDeleteAll(item->takeChildren()); } if (lv->isThinPool()) item->setExpanded(true); if (!is_sc && was_sc) { for (int x = 0; x < old_child_count; x++) { if (lv_name == item->child(x)->data(0, Qt::DisplayRole).toString()) was_expanded = item->child(x)->isExpanded(); } } if (is_sc) item->setData(4, Qt::UserRole, true); else item->setData(4, Qt::UserRole, false); item->setData(0, Qt::DisplayRole, lv_name); if (lv->isPartial()) { item->setIcon(0, QIcon::fromTheme(QStringLiteral("exclamation"))); item->setToolTip(0, i18n("one or more physical volumes are missing")); } else if (!is_sc && lv->getSnapshotCount() > 0) { item->setData(0, Qt::DisplayRole, QString(" %1").arg(lv_name)); item->setIcon(0, QIcon::fromTheme(QStringLiteral("bullet_star"))); item->setToolTip(0, i18n("origin")); } else { item->setIcon(0, QIcon()); item->setToolTip(0, QString()); } item->setData(1, Qt::DisplayRole, lv->getType()); if (is_sc) { item->setData(2, Qt::DisplayRole, KFormat().formatByteSize(lv->getTotalSize(), 1, dialect)); for (int x = 3; x < 12; x++) item->setData(x, Qt::DisplayRole, QVariant()); item->setIcon(3, QIcon()); item->setToolTip(3, QString()); item->setIcon(8, QIcon()); item->setToolTip(8, QString()); } else { item->setData(2, Qt::DisplayRole, KFormat().formatByteSize(lv->getSize(), 1, dialect)); if (lv->getFilesystemSize() > -1 && lv->getFilesystemUsed() > -1) { fs_remaining = lv->getFilesystemSize() - lv->getFilesystemUsed(); fs_percent = qRound(((double)fs_remaining / (double)lv->getFilesystemSize()) * 100); if (m_show_total) item->setData(3, Qt::DisplayRole, KFormat().formatByteSize(fs_remaining, 1, dialect)); else if (m_show_percent) item->setData(3, Qt::DisplayRole, QString("%%1").arg(fs_percent)); else if (m_show_both) item->setData(3, Qt::DisplayRole, QString(KFormat().formatByteSize(fs_remaining, 1, dialect) + " (%%1)").arg(fs_percent)); if (fs_percent <= m_fs_warn_percent) { item->setIcon(3, QIcon::fromTheme(QStringLiteral("dialog-warning"))); item->setToolTip(3, i18n("This filesystem is running out of space")); } else { item->setIcon(3, QIcon()); item->setToolTip(3, QString()); } } else { item->setData(3, Qt::DisplayRole, QString("")); item->setIcon(3, QIcon()); item->setToolTip(3, QString()); } item->setData(4, Qt::DisplayRole, lv->getFilesystem()); if (lv->isActive()) { if ((lv->isThinPool() || lv->isThinVolume()) && !lv->isSnapContainer()) item->setData(7, Qt::DisplayRole, QString("%%1").arg(lv->getDataPercent(), 1, 'f', 2)); else if ((lv->isPvmove() || lv->isRaid() || lv->isMirror()) && !lv->isSnapContainer()) item->setData(7, Qt::DisplayRole, QString("%%1").arg(lv->getCopyPercent(), 1, 'f', 2)); else if (lv->isCowSnap() || lv->isMerging()) item->setData(7, Qt::DisplayRole, QString("%%1").arg(lv->getSnapPercent(), 1, 'f', 2)); else item->setData(7, Qt::DisplayRole, QString("")); } else { item->setData(7, Qt::DisplayRole, QString("")); } item->setData(8, Qt::DisplayRole, lv->getState()); if (!lv->isSnapContainer() && !lv->isLvmMirrorLog() && !lv->isLvmMirrorLeg() && !lv->isVirtual() && !lv->isRaidImage() && !lv->isMetadata() && !lv->isThinPool() && !lv->isThinPoolData()) { if (lv->isMounted()) { item->setIcon(8, QIcon::fromTheme(QStringLiteral("emblem-mounted"))); item->setToolTip(8, i18n("mounted filesystem")); } else if (lv->getFilesystem() == "swap") { if (lv->isOpen()) { item->setIcon(8, QIcon::fromTheme(QStringLiteral("task-recurring"))); item->setToolTip(8, i18n("Active swap area")); } else { item->setIcon(8, QIcon::fromTheme(QStringLiteral("emblem-unmounted"))); item->setToolTip(8, i18n("Inactive swap area")); } } else { item->setIcon(8, QIcon::fromTheme(QStringLiteral("emblem-unmounted"))); item->setToolTip(8, i18n("unmounted filesystem")); } } if (lv->isWritable()) item->setData(9, Qt::DisplayRole, QString("r/w")); else item->setData(9, Qt::DisplayRole, QString("r/o")); item->setData(10, Qt::DisplayRole, lv->getTags().join(",")); item->setData(11, Qt::DisplayRole, lv->getMountPoints().join(",")); } item->setData(0, Qt::UserRole, lv_name); item->setData(2, Qt::UserRole, lv->getUuid()); if (lv->getSegmentCount() == 1) { item->setData(1, Qt::UserRole, 0); // 0 means segment 0 data present if (lv->isLvmMirror()) { item->setData(5, Qt::DisplayRole, QString("")); item->setData(6, Qt::DisplayRole, QString("")); } else { item->setData(5, Qt::DisplayRole, QString("%1").arg(lv->getSegmentStripes(0))); item->setData(6, Qt::DisplayRole, KFormat().formatByteSize(lv->getSegmentStripeSize(0), 1, dialect)); } if (!is_sc && old_type.contains("origin", Qt::CaseInsensitive)) { for (int x = 0; x < old_child_count; x++) { if (item->child(x)->data(0, Qt::DisplayRole) == lv->getName()) item->setExpanded(item->child(x)->isExpanded()); } } insertChildItems(lv, item); } else { item->setData(1, Qt::UserRole, -1); // -1 means not segment data item->setData(5, Qt::DisplayRole, QString("")); item->setData(6, Qt::DisplayRole, QString("")); insertSegmentItems(lv, item); } const int new_child_count = item->childCount(); if (is_sc) { // expand the item if it is a new snap container or snap count is different if (m_init) { item->setExpanded(true); } else { if (!was_sc || old_child_count != new_child_count) { item->setExpanded(true); if (!was_sc) { for (int x = 0; x < new_child_count; x++) { if (item->child(x)->data(0, Qt::UserRole) == lv_name) item->child(x)->setExpanded(was_expanded); } } } } } else if (was_sc && !is_sc) { // if it was formerly a snap container, set expanded to what the "real" lv was if (indexOfTopLevelItem(item) >= 0) addTopLevelItem(takeTopLevelItem(indexOfTopLevelItem(item))); // next line doesn't work without this! item->setExpanded(was_expanded); } for (int column = 1; column < item->columnCount() ; column++) item->setTextAlignment(column, Qt::AlignRight); return item; } void VGTree::setupContextMenu() { setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(popupContextMenu(QPoint))); } void VGTree::insertChildItems(LogVol *parentVolume, QTreeWidgetItem *parentItem) { LogVol *child_volume; const LvList immediate_children = parentVolume->getChildren(); for (int x = 0; x < immediate_children.size(); x++) { QTreeWidgetItem *child_item = nullptr; child_volume = immediate_children[x]; for (int y = parentItem->childCount() - 1; y >= 0; y--) { if (parentItem->child(y)->data(0, Qt::UserRole).toString() == child_volume->getName()) child_item = loadItem(child_volume, parentItem->child(y)); } if (child_item == nullptr) child_item = loadItem(child_volume, new QTreeWidgetItem(parentItem)); for (int column = 1; column < child_item->columnCount() ; column++) child_item->setTextAlignment(column, Qt::AlignRight); } // Remove child items for logical volumes that no longer exist for (int y = parentItem->childCount() - 1; y >= 0; y--) { bool match = false; for (int x = 0; x < immediate_children.size(); x++) { child_volume = immediate_children[x]; if (parentItem->child(y)->data(0, Qt::UserRole).toString() == child_volume->getName()) match = true; } if (!match) delete parentItem->takeChild(y); } return; } void VGTree::popupContextMenu(QPoint point) { emit lvMenuRequested(itemAt(point)); } void VGTree::setViewConfig() { KConfigSkeleton skeleton; bool volume, size, remaining, filesystem, type, stripes, stripesize, snapmove, state, access, tags, mountpoints; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", m_use_si_units, false); skeleton.setCurrentGroup("AllTreeColumns"); skeleton.addItemBool("show_percent", m_show_percent, false); skeleton.addItemBool("show_total", m_show_total, false); skeleton.addItemBool("show_both", m_show_both, true); skeleton.addItemInt("fs_warn", m_fs_warn_percent, 10); skeleton.setCurrentGroup("VolumeTreeColumns"); skeleton.addItemBool("vt_volume", volume, true); skeleton.addItemBool("vt_size", size, true); skeleton.addItemBool("vt_remaining", remaining, true); skeleton.addItemBool("vt_type", type, true); skeleton.addItemBool("vt_filesystem", filesystem, false); skeleton.addItemBool("vt_stripes", stripes, false); skeleton.addItemBool("vt_stripesize", stripesize, false); skeleton.addItemBool("vt_snapmove", snapmove, true); skeleton.addItemBool("vt_state", state, true); skeleton.addItemBool("vt_access", access, false); skeleton.addItemBool("vt_tags", tags, true); skeleton.addItemBool("vt_mountpoints", mountpoints, false); if (volume == isColumnHidden(0) || size == isColumnHidden(1) || remaining == isColumnHidden(2) || filesystem == isColumnHidden(3) || type == isColumnHidden(4) || stripes == isColumnHidden(5) || stripesize == isColumnHidden(6) || snapmove == isColumnHidden(7) || state == isColumnHidden(8) || access == isColumnHidden(9) || tags == isColumnHidden(10) || mountpoints == isColumnHidden(11)) { setColumnHidden(0, !volume); setColumnHidden(1, !type); setColumnHidden(2, !size); setColumnHidden(3, !remaining); setColumnHidden(4, !filesystem); setColumnHidden(5, !stripes); setColumnHidden(6, !stripesize); setColumnHidden(7, !snapmove); setColumnHidden(8, !state); setColumnHidden(9, !access); setColumnHidden(10, !tags); setColumnHidden(11, !mountpoints); for (int column = 0; column < 11; column++) { if (!isColumnHidden(column)) resizeColumnToContents(column); } } } void VGTree::adjustColumnWidth(QTreeWidgetItem *) { resizeColumnToContents(0); resizeColumnToContents(1); resizeColumnToContents(6); } void VGTree::insertSegmentItems(LogVol *lv, QTreeWidgetItem *item) { const int segment_count = lv->getSegmentCount(); const int child_count = item->childCount(); KFormat::BinaryUnitDialect dialect; if (m_use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; QTreeWidgetItem *child_item; QList segment_children; for (int x = 0; x < child_count ; x++) // segments can never have children segment_children.append(item->child(x)->takeChildren()); for (int x = segment_children.size() - 1; x >= 0 ; x--) delete segment_children[x]; // so delete them if (segment_count > child_count) { for (int x = 0; x < segment_count - child_count; x++) new QTreeWidgetItem(item); } else if (segment_count < child_count) { for (int x = child_count - 1; x >= segment_count ; x--) delete(item->takeChild(x)); } for (int segment = 0; segment < segment_count; segment++) { child_item = item->child(segment); if (lv->getPvNames(segment).contains("unknown device")) { child_item->setIcon(0, QIcon::fromTheme(QStringLiteral("exclamation"))); child_item->setToolTip(0, i18n("one or more physical volumes are missing")); } else { child_item->setIcon(0, QIcon()); child_item->setToolTip(0, QString()); } child_item->setData(0, Qt::DisplayRole, QString("Seg# %1").arg(segment)); child_item->setData(1, Qt::DisplayRole, QString("")); child_item->setData(2, Qt::DisplayRole, KFormat().formatByteSize(lv->getSegmentSize(segment), 1, dialect)); child_item->setData(3, Qt::DisplayRole, QString("")); child_item->setData(4, Qt::DisplayRole, QString("")); child_item->setData(5, Qt::DisplayRole, QString("%1").arg(lv->getSegmentStripes(segment))); child_item->setData(6, Qt::DisplayRole, KFormat().formatByteSize(lv->getSegmentStripeSize(segment), 1, dialect)); child_item->setData(0, Qt::UserRole, lv->getName()); child_item->setData(1, Qt::UserRole, segment); child_item->setData(2, Qt::UserRole, lv->getUuid()); child_item->setData(3, Qt::UserRole, QString("segment")); for (int column = 7; column < 12; column++) child_item->setData(column, Qt::DisplayRole, QString("")); for (int column = 1; column < child_item->columnCount() ; column++) child_item->setTextAlignment(column, Qt::AlignRight); } } kvpm-0.9.10/kvpm/kvpmconfigdialog.h0000644000175000017500000000355012770324126017462 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef KVPMCONFIGDIALOG_H #define KVPMCONFIGDIALOG_H #include class KConfigSkeleton; class KColorButton; class KEditListWidget; class QGroupBox; class QLabel; class QStackedWidget; class QString; class QTableWidget; class QTabWidget; class QWidget; class ExecutableFinder; class KvpmConfigDialog: public KConfigDialog { Q_OBJECT QTableWidget *m_executables_table; KEditListWidget *m_edit_list; KConfigSkeleton *m_skeleton; ExecutableFinder *m_executable_finder; QStackedWidget *m_color_stack = nullptr; KColorButton *m_primary_button = nullptr; KColorButton *m_logical_button = nullptr; QLabel *m_primary_label = nullptr; QLabel *m_logical_label = nullptr; QTabWidget *generalPage(); QWidget *colorsPage(); QWidget *fsColors(); QWidget *typeColors(); QWidget *otherColors(); QTabWidget *programsPage(); QGroupBox *allGroup(); QGroupBox *deviceGroup(); QGroupBox *logicalGroup(); QGroupBox *physicalGroup(); QGroupBox *pvPropertiesGroup(); QGroupBox *lvPropertiesGroup(); QGroupBox *devicePropertiesGroup(); QWidget *treesTab(); QWidget *propertiesTab(); void fillExecutablesTable(); public: KvpmConfigDialog(QWidget *parent, const QString name, KConfigSkeleton *const skeleton, ExecutableFinder *const executableFinder); ~KvpmConfigDialog(); public slots: void updateSettings(); void showOtherButtons(int index); }; #endif kvpm-0.9.10/kvpm/pvtree.h0000644000175000017500000000211012770324126015433 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVTREE_H #define PVTREE_H #include #include #include class QMenu; class QPoint; class VolGroup; class LogVol; class PhysVol; class PVTree : public QTreeWidget { Q_OBJECT VolGroup *m_vg; bool m_use_si_units; bool m_show_total; bool m_show_percent; bool m_show_both; int m_pv_warn_percent; QStringList getLvNames(PhysVol *const pv); void setViewConfig(); void setupContextMenu(); public: explicit PVTree(VolGroup *const group, QWidget *parent = 0); void loadData(); signals: void pvMenuRequested(QTreeWidgetItem *item); private slots: void popupContextMenu(QPoint point); }; #endif kvpm-0.9.10/kvpm/lvcreatebase.cpp0000644000175000017500000005476312770324126017146 0ustar benscottbenscott/* * * * Copyright (C) 2012, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvcreatebase.h" #include "mountentry.h" #include "processprogress.h" #include "sizeselectorbox.h" #include "volgroup.h" #include #include #include #include #include #include #include #include #include #include LvCreateDialogBase::LvCreateDialogBase(const VolGroup *const vg, long long maxFsSize, bool extend, bool snap, bool thin, bool ispool, QString name, QString pool, QWidget *parent): KvpmDialog(parent), m_vg(vg), m_extend(extend), m_ispool(ispool), m_maxfs_size(maxFsSize) { KConfigSkeleton skeleton; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", m_use_si_units, false); QWidget *const main_widget = new QWidget(); QVBoxLayout *const layout = new QVBoxLayout(); m_size_selector = nullptr; m_max_size = 0; bool show_name_tag = true; bool show_persistent = true; bool show_ro = true; bool show_zero = true; bool show_skip_sync = true; bool show_monitor = true; bool show_misc = true; if (extend) { show_name_tag = false; show_persistent = false; show_ro = false; show_zero = false; show_skip_sync = false; show_monitor = false; } else if (snap && !thin) { show_zero = false; show_skip_sync = false; } else if (thin) { show_zero = false; show_skip_sync = false; show_monitor = false; } m_tab_widget = new QTabWidget(this); m_tab_widget->addTab(createGeneralTab(show_name_tag, show_ro, show_zero, show_misc), i18nc("The standard common options", "General")); m_tab_widget->addTab(createAdvancedTab(show_persistent, show_skip_sync, show_monitor), i18nc("Less used, dangerous or complex options", "Advanced options")); QLabel *const banner1_label = new QLabel(); banner1_label->setAlignment(Qt::AlignCenter); layout->addWidget(banner1_label); if (!extend) { if (snap) { if (thin) { banner1_label->setText(i18n("Create a thin snapshot of: %1", name)); setCaption(i18n("Create A Thin Snapshot")); } else { banner1_label->setText(i18n("Create a snapshot of: %1", name)); setCaption(i18n("Create A Snapshot")); } } else { if (thin) { banner1_label->setText(i18n("Create a new thin volume")); setCaption(i18n("Create A New Thin Volume")); } else if (ispool) { banner1_label->setText(i18n("Create a new thin pool")); setCaption(i18n("Create A New Thin Pool")); } else { banner1_label->setText(i18n("Create a new logical volume")); setCaption(i18n("Create A New Logical Volume")); } } } else { if (thin) { banner1_label->setText(i18n("Extend thin volume: %1", name)); setCaption(i18n("Extend Thin Volume")); } else if (ispool) { banner1_label->setText(i18n("Extend thin pool: %1", name)); setCaption(i18n("Extend Thin Pool")); } else { banner1_label->setText(i18n("Extend volume: %1", name)); setCaption(i18n("Extend Volume")); } } if (thin) { QLabel *const banner2_label = new QLabel(i18n("Pool: %1", pool)); banner2_label->setAlignment(Qt::AlignCenter); layout->addWidget(banner2_label); } layout->addSpacing(5); layout->addWidget(m_tab_widget); main_widget->setLayout(layout); setMainWidget(main_widget); if (!extend) { connect(m_persistent_box, SIGNAL(toggled(bool)), this, SLOT(resetOkButton())); connect(m_major_edit, SIGNAL(textEdited(QString)), this, SLOT(resetOkButton())); connect(m_minor_edit, SIGNAL(textEdited(QString)), this, SLOT(resetOkButton())); connect(m_name_edit, SIGNAL(textEdited(QString)), this, SLOT(resetOkButton())); connect(m_tag_edit, SIGNAL(textEdited(QString)), this, SLOT(resetOkButton())); } } QWidget* LvCreateDialogBase::createGeneralTab(bool showNameTag, bool showRo, bool showZero, bool showMisc) { QWidget *const general_tab = new QWidget(this); QHBoxLayout *const layout = new QHBoxLayout; general_tab->setLayout(layout); m_general_layout = new QVBoxLayout(); layout->addStretch(); layout->addLayout(m_general_layout); layout->addStretch(); QGroupBox *const volume_box = new QGroupBox(); QVBoxLayout *const volume_layout = new QVBoxLayout; volume_box->setLayout(volume_layout); m_general_layout->addWidget(volume_box); if (showNameTag) { QHBoxLayout *const name_layout = new QHBoxLayout(); m_name_edit = new QLineEdit(); QRegExp rx("[0-9a-zA-Z_\\.][-0-9a-zA-Z_\\.]*"); m_name_validator = new QRegExpValidator(rx, m_name_edit); m_name_edit->setValidator(m_name_validator); QLabel *const name_label = new QLabel(i18n("Volume name: ")); if (m_ispool) name_label->setText(i18n("Pool name (required): ")); name_label->setBuddy(m_name_edit); name_layout->addWidget(name_label); name_layout->addWidget(m_name_edit); volume_layout->insertLayout(0, name_layout); QHBoxLayout *const tag_layout = new QHBoxLayout(); m_tag_edit = new QLineEdit(); QRegExp rx2("[0-9a-zA-Z_\\.+-]*"); m_tag_validator = new QRegExpValidator(rx2, m_tag_edit); m_tag_edit->setValidator(m_tag_validator); QLabel *const tag_label = new QLabel(i18n("Tag (optional): ")); tag_label->setBuddy(m_tag_edit); tag_layout->addWidget(tag_label); tag_layout->addWidget(m_tag_edit); volume_layout->insertLayout(1, tag_layout); } else { m_name_edit = nullptr; m_tag_edit = nullptr; volume_box->hide(); } QGroupBox *const misc_box = new QGroupBox(); QVBoxLayout *const misc_layout = new QVBoxLayout(); misc_box->setLayout(misc_layout); m_readonly_check = new QCheckBox(i18n("Set read only")); m_zero_check = new QCheckBox(i18n("Write zeros at volume start")); misc_layout->addWidget(m_readonly_check); misc_layout->addWidget(m_zero_check); if (!showRo) m_readonly_check->hide(); if (!showZero) m_zero_check->hide(); if (showZero && showRo) { connect(m_readonly_check, SIGNAL(toggled(bool)), this, SLOT(zeroReadOnlyEnable())); connect(m_zero_check, SIGNAL(toggled(bool)), this, SLOT(zeroReadOnlyEnable())); } m_extend_fs_check = new QCheckBox(i18n("Extend filesystem with volume")); misc_layout->addWidget(m_extend_fs_check); if (!m_extend || m_ispool) { m_extend_fs_check->setChecked(false); m_extend_fs_check->setEnabled(false); m_extend_fs_check->hide(); } else { if (m_maxfs_size < 0) { m_extend_fs_check->setChecked(false); m_extend_fs_check->setEnabled(false); } else { m_extend_fs_check->setChecked(true); connect(m_extend_fs_check, SIGNAL(toggled(bool)), this, SIGNAL(extendFs())); } } misc_layout->addSpacing(10); m_warning_widget = createWarningWidget(); misc_layout->addWidget(m_warning_widget); m_warning_widget->hide(); m_current_label = new QLabel(); m_extend_label = new QLabel(); m_maxfs_size_label = new QLabel(); m_stripes_label = new QLabel(); m_max_size_label = new QLabel(); misc_layout->addWidget(m_current_label); misc_layout->addWidget(m_extend_label); misc_layout->addWidget(m_max_size_label); misc_layout->addWidget(m_maxfs_size_label); misc_layout->addWidget(m_stripes_label); if (!m_extend) { m_current_label->hide(); m_extend_label->hide(); m_maxfs_size_label->hide(); } misc_layout->addStretch(); m_general_layout->addWidget(misc_box); if (!showMisc) misc_box->hide(); return general_tab; } QWidget* LvCreateDialogBase::createAdvancedTab(bool showPersistent, bool showSkipSync, bool showMonitor) { QWidget *const advanced_tab = new QWidget(this); QHBoxLayout *const advanced_layout = new QHBoxLayout; advanced_tab->setLayout(advanced_layout); QGroupBox *const options_box = new QGroupBox(); QVBoxLayout *const layout = new QVBoxLayout; QVBoxLayout *const options_layout = new QVBoxLayout; options_box->setLayout(options_layout); layout->addWidget(options_box); advanced_layout->addStretch(); advanced_layout->addLayout(layout); advanced_layout->addStretch(); m_monitor_check = new QCheckBox(i18n("Monitor with dmeventd")); m_skip_sync_check = new QCheckBox(i18n("Skip initial synchronization of mirror")); m_skip_sync_check->setChecked(false); options_layout->addWidget(m_monitor_check); options_layout->addWidget(m_skip_sync_check); if (!showSkipSync) m_skip_sync_check->hide(); if (!showMonitor) m_monitor_check->hide(); m_udevsync_check = new QCheckBox(i18n("Synchronize with udev")); m_udevsync_check->setChecked(true); options_layout->addWidget(m_udevsync_check); if (showPersistent) { QVBoxLayout *const persistent_layout = new QVBoxLayout; QHBoxLayout *const minor_number_layout = new QHBoxLayout; QHBoxLayout *const major_number_layout = new QHBoxLayout; m_minor_edit = new QLineEdit(); m_major_edit = new QLineEdit(); QLabel *const minor_number = new QLabel(i18n("Device minor number: ")); QLabel *const major_number = new QLabel(i18n("Device major number: ")); minor_number->setBuddy(m_minor_edit); major_number->setBuddy(m_major_edit); major_number_layout->addWidget(major_number); major_number_layout->addWidget(m_major_edit); minor_number_layout->addWidget(minor_number); minor_number_layout->addWidget(m_minor_edit); persistent_layout->addLayout(major_number_layout); persistent_layout->addLayout(minor_number_layout); QIntValidator *const minor_validator = new QIntValidator(m_minor_edit); QIntValidator *const major_validator = new QIntValidator(m_major_edit); minor_validator->setBottom(0); major_validator->setBottom(0); m_minor_edit->setValidator(minor_validator); m_major_edit->setValidator(major_validator); m_persistent_box = new QGroupBox(i18n("Use persistent device numbering")); m_persistent_box->setCheckable(true); m_persistent_box->setChecked(false); m_persistent_box->setLayout(persistent_layout); layout->addWidget(m_persistent_box); } else { m_persistent_box = nullptr; m_minor_edit = nullptr; m_major_edit = nullptr; } layout->addStretch(); advanced_layout->addStretch(); return advanced_tab; } void LvCreateDialogBase::setReadOnly(bool ro) { m_readonly_check->setChecked(ro); } void LvCreateDialogBase::setSkipSync(bool skip) { m_skip_sync_check->setChecked(skip); } void LvCreateDialogBase::setZero(bool zero) { m_zero_check->setChecked(zero); } void LvCreateDialogBase::setMonitor(bool monitor) { m_monitor_check->setChecked(monitor); } void LvCreateDialogBase::enableZero(bool zero) { m_zero_check->setEnabled(zero); } void LvCreateDialogBase::enableSkipSync(bool skip) { m_skip_sync_check->setEnabled(skip); } void LvCreateDialogBase::enableMonitor(bool monitor) { m_monitor_check->setEnabled(monitor); } void LvCreateDialogBase::setUdev(bool udev) { m_udevsync_check->setChecked(udev); } bool LvCreateDialogBase::getMonitor() { return m_monitor_check->isChecked(); } bool LvCreateDialogBase::getUdev() { return m_udevsync_check->isChecked(); } // Returns true if the selected size is smaller than the current // size when extending or less than zero for a new volume. // NOTE: returns false for size zero new volumes and volumes // of the exact same size when extending! The function isValid() // will return false for those conditions however. bool LvCreateDialogBase::isLow() { if (m_size_selector != nullptr) { if (m_size_selector->getNewSize() == -1) { // if the size selector line edit is empty return false; } else if (!m_size_selector->isValid()) { if (m_extend) { if (m_size_selector->getNewSize() >= m_size_selector->getMinimumSize()) return false; else return true; } else { if (m_size_selector->getNewSize() >= 0) return false; else return true; } } } return false; } bool LvCreateDialogBase::isValid() { bool valid = false; bool valid_name = true; bool valid_tag = true; bool valid_persistent = true; if (!m_extend) { int pos = 0; QString name = m_name_edit->text(); if (m_name_validator->validate(name, pos) == QValidator::Acceptable && name != "." && name != "..") valid_name = true; else if (name.isEmpty() && !m_ispool) valid_name = true; else valid_name = false; QString tag = m_tag_edit->text(); if (m_tag_validator->validate(tag, pos) == QValidator::Acceptable) valid_tag = true; else if (tag.isEmpty() && !m_ispool) valid_tag = true; else valid_tag = false; QString major = m_major_edit->text(); QString minor = m_minor_edit->text(); const QValidator *const major_validator = m_major_edit->validator(); const QValidator *const minor_validator = m_minor_edit->validator(); const bool valid_major = (major_validator->validate(major, pos) == QValidator::Acceptable); const bool valid_minor = (minor_validator->validate(minor, pos) == QValidator::Acceptable); if (m_persistent_box->isChecked() && !(valid_major && valid_minor)) valid_persistent = false; else valid_persistent = true; } if (!valid_persistent || !valid_name || !valid_tag) { valid = false; } else if (m_size_selector == nullptr) { valid = true; } else if (m_size_selector->isValid()) { if (m_extend) { if (m_size_selector->getNewSize() > m_size_selector->getMinimumSize()) valid = true; else valid = false; } else { if (m_size_selector->getNewSize() > 0) valid = true; else valid = false; } } else { valid = false; } return valid; } bool LvCreateDialogBase::getReadOnly() { return m_readonly_check->isChecked(); } bool LvCreateDialogBase::getZero() { return m_zero_check->isChecked(); } bool LvCreateDialogBase::getSkipSync() { return m_skip_sync_check->isChecked(); } QString LvCreateDialogBase::getMajor() { return m_major_edit->text(); } QString LvCreateDialogBase::getMinor() { return m_minor_edit->text(); } long long LvCreateDialogBase::getSelectorExtents() { return m_size_selector->getNewSize(); } QString LvCreateDialogBase::getName() { if (m_name_edit != nullptr) return m_name_edit->text(); else return QString(); } QString LvCreateDialogBase::getTag() { if (m_tag_edit != nullptr) return m_tag_edit->text(); else return QString(); } bool LvCreateDialogBase::getPersistent() { return m_persistent_box->isChecked(); } void LvCreateDialogBase::initializeSizeSelector(long long extentSize, long long currentExtents, long long maxExtents) { if (currentExtents >= m_maxfs_size / extentSize) { m_maxfs_size = -1; m_extend_fs_check->setChecked(false); m_extend_fs_check->setEnabled(false); } m_size_selector = new SizeSelectorBox(extentSize, currentExtents, maxExtents, currentExtents, true, false); m_general_layout->insertWidget(1, m_size_selector); connect(m_size_selector, SIGNAL(stateChanged()), this, SLOT(resetOkButton())); connect(m_size_selector, SIGNAL(stateChanged()), this, SLOT(setSizeLabels())); KFormat::BinaryUnitDialect dialect; if (m_use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; m_current_label->setText(i18n("Current size: %1", KFormat().formatByteSize(currentExtents * extentSize, 1, dialect))); } void LvCreateDialogBase::setSelectorMaxExtents(long long max) { if (m_size_selector != nullptr) m_size_selector->setConstrainedMax(max); } void LvCreateDialogBase::zeroReadOnlyEnable() { disconnect(m_readonly_check, SIGNAL(toggled(bool)), this, SLOT(zeroReadOnlyEnable())); disconnect(m_zero_check, SIGNAL(toggled(bool)), this, SLOT(zeroReadOnlyEnable())); if (m_zero_check->isChecked()) { m_readonly_check->setChecked(false); m_readonly_check->setEnabled(false); } else m_readonly_check->setEnabled(true); if (m_readonly_check->isChecked()) { m_zero_check->setChecked(false); m_zero_check->setEnabled(false); } else m_zero_check->setEnabled(true); resetOkButton(); connect(m_readonly_check, SIGNAL(toggled(bool)), this, SLOT(zeroReadOnlyEnable())); connect(m_zero_check, SIGNAL(toggled(bool)), this, SLOT(zeroReadOnlyEnable())); } void LvCreateDialogBase::setPhysicalTab(QWidget *const tab) { m_tab_widget->insertTab(1, tab, i18n("Physical layout")); } QWidget* LvCreateDialogBase::createWarningWidget() { QWidget *const widget = new QWidget(); QHBoxLayout *const layout = new QHBoxLayout(); QLabel *const exclamation = new QLabel(); exclamation->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(32, 32)); m_warning_label = new QLabel(); layout->addWidget(exclamation); layout->addWidget(m_warning_label); layout->addStretch(); widget->setLayout(layout); return widget; } void LvCreateDialogBase::setWarningLabel(const QString message) { m_warning_widget->show(); m_warning_label->setText(message); } void LvCreateDialogBase::clearWarningLabel() { m_warning_label->setText(""); m_warning_widget->hide(); } const VolGroup* LvCreateDialogBase::getVg() { return m_vg; } void LvCreateDialogBase::setSizeLabels() { KFormat::BinaryUnitDialect dialect; if (m_use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; if (m_size_selector != nullptr) { const long long extend = m_size_selector->getNewSize() - m_size_selector->getMinimumSize(); const long long extent_size = m_vg->getExtentSize(); if (m_size_selector->usingBytes()) { if (extend >= 0) m_extend_label->setText(i18n("Increasing size: +%1", KFormat().formatByteSize(extend * extent_size, 1, dialect))); else m_extend_label->setText(i18n("Increasing size: %1", KFormat().formatByteSize(extend * extent_size, 1, dialect))); } else { if (extend >= 0) m_extend_label->setText(i18n("Increasing extents: +%1", extend)); else m_extend_label->setText(i18n("Increasing extents: %1", extend)); } if (m_maxfs_size < 0) { if (m_size_selector->usingBytes()) { m_max_size_label->setText(i18n("Maximum volume size: %1", KFormat().formatByteSize(m_max_size, 1, dialect))); } else { m_max_size_label->setText(i18n("Maximum volume extents: %1", m_max_size / extent_size)); } } else { if (m_size_selector->usingBytes()) { m_max_size_label->setText(i18n("Maximum volume size: %1", KFormat().formatByteSize(m_max_size, 1, dialect))); m_maxfs_size_label->setText(i18n("Maximum filesystem size: %1", KFormat().formatByteSize(m_maxfs_size, 1, dialect))); } else { m_max_size_label->setText(i18n("Maximum volume extents: %1", m_max_size / extent_size)); m_maxfs_size_label->setText(i18n("Maximum filesystem extents: %1", m_maxfs_size / extent_size)); } } } else { m_extend_label->hide(); m_max_size_label->hide(); m_maxfs_size_label->hide(); } } void LvCreateDialogBase::setInfoLabels(VolumeType type, int stripes, int mirrors, long long maxSize) { m_max_size = maxSize; setSizeLabels(); if (m_size_selector != nullptr) { m_stripes_label->show(); if (type == LINEAR) { if (stripes > 1) m_stripes_label->setText(i18n("(with %1 stripes)", stripes)); else m_stripes_label->setText(i18n("(linear volume)")); } else if (type == LVMMIRROR) { if (stripes > 1) m_stripes_label->setText(i18n("(LVM2 mirror with %1 legs and %2 stripes)", mirrors, stripes)); else m_stripes_label->setText(i18n("(LVM2 mirror with %1 legs)", mirrors)); } else if (type == RAID1) { m_stripes_label->setText(i18n("(RAID 1 mirror with %1 legs)", mirrors)); } else if (type == RAID4) { m_stripes_label->setText(i18n("(RAID 4 with %1 stripes + 1 parity)", stripes)); } else if (type == RAID5) { m_stripes_label->setText(i18n("(RAID 5 with %1 stripes + 1 parity)", stripes)); } else if (type == RAID5) { m_stripes_label->setText(i18n("(RAID 6 with %1 stripes + 2 parity)", stripes)); } } else { m_stripes_label->hide(); } } long long LvCreateDialogBase::getMaxFsSize() { return m_maxfs_size; } bool LvCreateDialogBase::getExtendFs() { return m_extend_fs_check->isChecked(); } kvpm-0.9.10/kvpm/allocationpolicy.cpp0000644000175000017500000001053712770324126020042 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "allocationpolicy.h" #include #include #include #include #include QString policyToLocalString(AllocationPolicy policy) { switch(policy) { case NO_POLICY: return QString(""); break; case NORMAL: return QString(i18n("normal")); break; case CONTIGUOUS: return QString(i18n("contiguous")); break; case CLING: return QString(i18n("cling")); break; case ANYWHERE: return QString(i18n("anywhere")); break; case INHERIT_NORMAL: return QString(i18n("inherit (normal)")); break; case INHERIT_CONTIGUOUS: return QString(i18n("inherit (contiguous)")); break; case INHERIT_CLING: return QString(i18n("inherit (cling)")); break; case INHERIT_ANYWHERE: return QString(i18n("inherit (anywhere)")); break; default: return QString(i18n("normal")); } } QString policyToString(AllocationPolicy policy) { switch(policy) { case NO_POLICY: return QString(""); break; case NORMAL: return QString("normal"); break; case CONTIGUOUS: return QString("contiguous"); break; case CLING: return QString("cling"); break; case ANYWHERE: return QString("anywhere"); break; case INHERIT_NORMAL: case INHERIT_CONTIGUOUS: case INHERIT_CLING: case INHERIT_ANYWHERE: return QString("inherit"); break; default: return QString("normal"); } } PolicyComboBox::PolicyComboBox(AllocationPolicy policy, AllocationPolicy vgpolicy, QWidget *parent) : QWidget(parent), m_vg_policy(vgpolicy) { QLabel *const label = new QLabel(i18n("Allocation policy: ")); m_combo = new QComboBox(); m_combo->addItem(i18n("Normal")); m_combo->addItem(i18n("Contiguous")); m_combo->addItem(i18n("Cling")); m_combo->addItem(i18n("Anywhere")); if (m_vg_policy != NO_POLICY) m_combo->addItem(i18n("Inherit (%1)", policyToString(vgpolicy))); if (policy == NORMAL) m_combo->setCurrentIndex(0); else if (policy == CONTIGUOUS) m_combo->setCurrentIndex(1); else if (policy == CLING) m_combo->setCurrentIndex(2); else if (policy == ANYWHERE) m_combo->setCurrentIndex(3); else if ((policy > ANYWHERE) && (m_combo->count() > 4)) m_combo->setCurrentIndex(4); else m_combo->setCurrentIndex(0); QHBoxLayout *const layout = new QHBoxLayout; layout->addWidget(label); layout->addWidget(m_combo); connect(m_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(emitNewPolicy())); setLayout(layout); } void PolicyComboBox::emitNewPolicy() { emit policyChanged(getPolicy()); } AllocationPolicy PolicyComboBox::getPolicy() { switch(m_combo->currentIndex()) { case 0: return NORMAL; break; case 1: return CONTIGUOUS; break; case 2: return CLING; break; case 3: return ANYWHERE; break; case 4: switch(m_vg_policy) { case NORMAL: return INHERIT_NORMAL; break; case CONTIGUOUS: return INHERIT_CONTIGUOUS; break; case CLING: return INHERIT_CLING; break; case ANYWHERE: return INHERIT_ANYWHERE; break; default: return INHERIT_NORMAL; break; } default: return NORMAL; } } AllocationPolicy PolicyComboBox::getEffectivePolicy() { switch(m_combo->currentIndex()) { case 0: return NORMAL; break; case 1: return CONTIGUOUS; break; case 2: return CLING; break; case 3: return ANYWHERE; break; case 4: return m_vg_policy; break; default: return NORMAL; } } kvpm-0.9.10/kvpm/lvcreate.h0000644000175000017500000000516012770324126015743 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVCREATE_H #define LVCREATE_H #include #include #include "lvcreatebase.h" class QSpinBox; class QComboBox; class QGroupBox; class LogVol; class PvGroupBox; class PvSpace; class LVCreateDialog : public LvCreateDialogBase { Q_OBJECT bool m_ispool; // TRUE if a thin pool bool m_snapshot; // TRUE if a snapshot bool m_extend; // TRUE if extending a volume bool m_use_si_units; // TRUE Metric SI sizes = MB and GB, otherise use MiB, GiB etc. LogVol *m_lv; // origin for snap or lv to extend // set to NULL if creating a new logical volume QWidget *m_physical_tab; // The physical tab QGroupBox *m_volume_box; PvGroupBox *m_pv_box; QComboBox *m_stripe_size_combo, *m_type_combo, *m_log_combo, *m_chunk_combo; QSpinBox *m_mirror_count_spin, // how many mirrors we want *m_stripe_count_spin; // how many stripes we want QWidget *m_stripe_widget, *m_mirror_widget; void buildDialog(); QWidget* createPhysicalTab(); QWidget* createTypeWidget(int pvcount); QWidget* createStripeWidget(); QWidget* createChunkWidget(); QWidget* createMirrorWidget(int pvcount); long long getLargestVolume(); int getNeededStripes(); int getLogCount(); int getMaxStripes(); int getChunkSize(); int getChunkSize(long long const volumeSize); void makeConnections(); void extendLastSegment(QList &committed, QList &available); long long roundExtentsToStripes(long long extents); bool hasInitialErrors(); bool getPvsByPolicy(QList &usableBytes); bool reservePoolMetadata(QList &usableBytes); QList> getPvSpaceList(); QStringList args(); public: LVCreateDialog(VolGroup *const vg, bool ispool, QWidget *parent = nullptr); LVCreateDialog(LogVol *const volume, const bool snapshot, QWidget *parent = nullptr); private slots: void setMaxSize(); void resetOkButton(); void enableMonitoring(int index); void enableTypeOptions(int index); void enableStripeCombo(int value); void commit(); }; #endif kvpm-0.9.10/kvpm/pvtree.cpp0000644000175000017500000002203212770324126015773 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvtree.h" #include #include #include #include #include #include #include #include "logvol.h" #include "masterlist.h" #include "processprogress.h" #include "pvmove.h" #include "pvchange.h" #include "physvol.h" #include "topwindow.h" #include "volgroup.h" /* This is the physical volume tree list on the volume group tab */ PVTree::PVTree(VolGroup *const group, QWidget *parent) : QTreeWidget(parent), m_vg(group) { QStringList header_labels; setColumnCount(6); QTreeWidgetItem *item; header_labels << i18nc("The name of the device", "Name") << i18n("Size") << i18nc("Unused space", "Remaining") << i18nc("Space used up", "Used") << i18n("State") << i18n("Allocatable") << i18n("Tags") << i18n("Logical volumes"); item = new QTreeWidgetItem(static_cast(nullptr), header_labels); for (int column = 0; column < 7; column++) item->setTextAlignment(column, Qt::AlignCenter); sortByColumn(0, Qt::AscendingOrder); item->setToolTip(0, i18n("Physical volume device")); item->setToolTip(1, i18n("Total size of physical volume")); item->setToolTip(2, i18n("Free space on physical volume")); item->setToolTip(3, i18n("Space used on physical volume")); item->setToolTip(4, i18n("A physcial volume is active if it has logical volumes that are active")); item->setToolTip(5, i18n("If physical volume allows more extents to be allocated")); item->setToolTip(6, i18n("Optional tags for physical volume")); item->setToolTip(7, i18n("Logical volumes on physical volume")); setHeaderItem(item); setupContextMenu(); } void PVTree::loadData() { QList pv_tree_items; QString old_current_pv_name; if (currentItem()) old_current_pv_name = currentItem()->data(0, 0).toString(); clear(); setSortingEnabled(false); setViewConfig(); KFormat::BinaryUnitDialect dialect; if (m_use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; for (auto pv : m_vg->getPhysicalVolumes()) { QStringList pv_data; const QString device_name = pv->getMapperName(); if (pv->isMissing()) { if (device_name == "unknown device") pv_data << i18n("MISSING"); else pv_data << i18n("MISSING %1", device_name); } else { pv_data << device_name; } pv_data << KFormat().formatByteSize(pv->getSize(), 1, dialect); if (m_show_total && !m_show_percent) { pv_data << KFormat().formatByteSize(pv->getRemaining(), 1, dialect); pv_data << KFormat().formatByteSize(pv->getSize() - pv->getRemaining(), 1, dialect); } else if (!m_show_total && m_show_percent) { pv_data << QString("%%1").arg(100 - pv->getPercentUsed()); pv_data << QString("%%1").arg(pv->getPercentUsed()); } else if (m_show_both) { pv_data << QString("%1 (%%2) ").arg(KFormat().formatByteSize(pv->getRemaining(), 1, dialect)).arg(100 - pv->getPercentUsed()); pv_data << QString("%1 (%%2) ").arg(KFormat().formatByteSize(pv->getSize() - pv->getRemaining(), 1, dialect)).arg(pv->getPercentUsed()); } if (pv->isActive()) pv_data << "Active"; else pv_data << "Inactive"; if (pv->isAllocatable()) pv_data << "Yes"; else pv_data << "No"; pv_data << pv->getTags().join(", "); pv_data << getLvNames(pv).join(", "); QTreeWidgetItem *const item = new QTreeWidgetItem(static_cast(nullptr), pv_data); if (pv->isMissing()) { item->setIcon(0, QIcon::fromTheme(QStringLiteral("exclamation"))); item->setToolTip(0, i18n("This physical volume can not be found")); } else { item->setIcon(0, QIcon()); } item->setData(0, Qt::UserRole, pv->getUuid()); item->setData(1, Qt::UserRole, pv->getSize()); item->setData(2, Qt::UserRole, pv->getRemaining()); item->setData(3, Qt::UserRole, (pv->getSize() - pv->getRemaining())); if (m_pv_warn_percent && (m_pv_warn_percent >= (100 - pv->getPercentUsed()))) { item->setIcon(2, QIcon::fromTheme(QStringLiteral("dialog-warning"))); item->setToolTip(2, i18n("Physical volume that is running out of space")); item->setIcon(3, QIcon::fromTheme(QStringLiteral("dialog-warning"))); item->setToolTip(3, i18n("Physical volume that is running out of space")); } if (pv->isActive()) { item->setToolTip(4, i18n("Active")); item->setIcon(4, QIcon::fromTheme(QStringLiteral("lightbulb"))); } else { item->setToolTip(4, i18n("Inactive")); item->setIcon(4, QIcon::fromTheme(QStringLiteral("lightbulb_off"))); } for (int column = 1; column < 6; column++) item->setTextAlignment(column, Qt::AlignRight); item->setTextAlignment(6, Qt::AlignLeft); item->setTextAlignment(7, Qt::AlignLeft); pv_tree_items.append(item); } insertTopLevelItems(0, pv_tree_items); setSortingEnabled(true); for (int column = 0; column < 7; ++column) { if (!isColumnHidden(column)) resizeColumnToContents(column); } if (!pv_tree_items.isEmpty() && !old_current_pv_name.isEmpty()) { bool match = false; for (int x = pv_tree_items.size() - 1; x >= 0; --x) { if (old_current_pv_name == pv_tree_items[x]->data(0, 0).toString()) { setCurrentItem(pv_tree_items[x]); match = true; break; } } if (!match) { setCurrentItem(pv_tree_items[0]); scrollToItem(pv_tree_items[0], QAbstractItemView::EnsureVisible); } } else if (!pv_tree_items.isEmpty()) { setCurrentItem(pv_tree_items[0]); scrollToItem(pv_tree_items[0], QAbstractItemView::EnsureVisible); } return; } void PVTree::setupContextMenu() { setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(popupContextMenu(QPoint))); } void PVTree::popupContextMenu(QPoint point) { emit pvMenuRequested(itemAt(point)); } void PVTree::setViewConfig() { KConfigSkeleton skeleton; bool pvname, pvsize, pvremaining, pvused, pvstate, pvallocate, pvtags, pvlvnames; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", m_use_si_units, false); skeleton.setCurrentGroup("PhysicalTreeColumns"); skeleton.addItemBool("pt_name", pvname, true); skeleton.addItemBool("pt_size", pvsize, true); skeleton.addItemBool("pt_remaining", pvremaining, true); skeleton.addItemBool("pt_used", pvused, false); skeleton.addItemBool("pt_state", pvstate, false); skeleton.addItemBool("pt_allocate", pvallocate, true); skeleton.addItemBool("pt_tags", pvtags, true); skeleton.addItemBool("pt_lvnames", pvlvnames, true); skeleton.setCurrentGroup("AllTreeColumns"); skeleton.addItemBool("show_total", m_show_total, false); skeleton.addItemBool("show_percent", m_show_percent, false); skeleton.addItemBool("show_both", m_show_both, true); skeleton.addItemInt("pv_warn", m_pv_warn_percent, 0); if (!(!pvname == isColumnHidden(0) && !pvsize == isColumnHidden(1) && !pvremaining == isColumnHidden(2) && !pvused == isColumnHidden(3) && !pvstate == isColumnHidden(4) && !pvallocate == isColumnHidden(5) && !pvtags == isColumnHidden(6) && !pvlvnames == isColumnHidden(7))) { setColumnHidden(0, !pvname); setColumnHidden(1, !pvsize); setColumnHidden(2, !pvremaining); setColumnHidden(3, !pvused); setColumnHidden(4, !pvstate); setColumnHidden(5, !pvallocate); setColumnHidden(6, !pvtags); setColumnHidden(7, !pvlvnames); } } /* here we get the names of logical volumes associated With the physical volume */ QStringList PVTree::getLvNames(PhysVol *const pv) { const QString current_name = pv->getMapperName(); QStringList lv_names; for (auto lv : m_vg->getLogicalVolumesFlat()) { for (auto pv_name : lv->getPvNamesAll()) { if (current_name == pv_name) lv_names << lv->getName(); } } lv_names.sort(); lv_names.removeDuplicates(); return lv_names; } kvpm-0.9.10/kvpm/vgremove.cpp0000644000175000017500000000412712770324126016325 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgremove.h" #include #include #include #include #include "masterlist.h" #include "progressbox.h" #include "topwindow.h" #include "volgroup.h" bool remove_vg(VolGroup *volumeGroup) { const QByteArray vg_name = volumeGroup->getName().toLocal8Bit(); lvm_t lvm = MasterList::getLvm(); vg_t vg_dm = NULL; ProgressBox *const progress_box = TopWindow::getProgressBox(); bool success = true; const QString message = i18n("Are you certain you want to delete volume group: %1?", volumeGroup->getName()); if (KMessageBox::questionYesNo(0, message) == KMessageBox::Yes) { progress_box->setRange(0, 3); progress_box->setValue(1); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if ((vg_dm = lvm_vg_open(lvm, vg_name.data(), "w", 0))) { progress_box->setValue(2); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if (lvm_vg_remove(vg_dm)) { KMessageBox::error(0, QString(lvm_errmsg(lvm))); success = false; } else { if (lvm_vg_write(vg_dm)) { KMessageBox::error(0, QString(lvm_errmsg(lvm))); success = false; } } lvm_vg_close(vg_dm); progress_box->setValue(3); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } else { KMessageBox::error(0, QString(lvm_errmsg(lvm))); success = false; } } else { success = false; } progress_box->setValue(3); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); return success; } kvpm-0.9.10/kvpm/logvol.cpp0000644000175000017500000007651012770324126016002 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "logvol.h" #include #include #include #include #include #include #include #include #include "fsdata.h" #include "fsprobe.h" #include "masterlist.h" #include "physvol.h" #include "storagedevice.h" #include "volgroup.h" /* Some information about a logical volume pertains to the entire volume while other information only applies to a segement in the volume. The volume keeps a list of Segment structures for segment information */ struct Segment { int stripes; // number of stripes in segment int stripe_size; long long size; // segment size (bytes) QString type; // linear, raid etc. QStringList device_path; // full path of physical volume QList starting_extent; // first extent on physical volume // for this segment QString discards; // May be: ignore, nopassdown, passdown long long chunk_size; // Thin pool chunk size }; LogVol::LogVol(lv_t lvmLv, vg_t lvmVg, const VolGroup *const vg, LogVol *const lvParent, MountTables *const tables, const bool orphan) : m_vg(vg), m_tables(tables), m_lv_parent(lvParent), m_orphan(orphan) { m_snap_container = false; rescan(lvmLv, lvmVg); } LogVol::~LogVol() { for (auto ptr : m_segments) delete ptr; } void LogVol::rescan(lv_t lvmLv, vg_t lvmVg) { bool was_snap_container = m_snap_container; m_snap_container = false; m_under_conversion = false; m_is_origin = false; // traditional snap origin - not thin snap origin m_merging = false; m_metadata = false; m_raid_metadata = false; m_thin_metadata = false; m_raidmirror = false; m_raidmirror_leg = false; m_lvmmirror = false; m_lvmmirror_leg = false; m_lvmmirror_log = false; m_raid = false; m_raid_image = false; m_cow_snap = false; m_thin_snap = false; m_temp = false; m_thin = false; m_thin_data = false; m_thin_pool = false; m_pvmove = false; m_valid = true; m_synced = true; m_virtual = false; m_copy_percent = 0; m_data_percent = 0; m_snap_percent = 0; m_lv_name = QString(lvm_lv_get_property(lvmLv, "lv_name").value.string).trimmed(); m_lv_full_name = m_vg->getName() + '/' + m_lv_name; // m_lv_mapper_path = QString(lvm_lv_get_property(lvmLv, "lv_path").value.string).trimmed(); // borked! m_lv_mapper_path = QString("/dev/").append(m_lv_full_name); // workaround m_log = QString(lvm_lv_get_property(lvmLv, "mirror_log").value.string).trimmed(); m_pool = QString(lvm_lv_get_property(lvmLv, "pool_lv").value.string).trimmed(); m_tags = QString(lvm_lv_get_property(lvmLv, "lv_tags").value.string).split(',', QString::SkipEmptyParts); m_size = lvm_lv_get_property(lvmLv, "lv_size").value.integer; m_extents = m_size / m_vg->getExtentSize(); if (lvm_lv_get_property(lvmLv, "lv_kernel_major").is_valid && lvm_lv_get_property(lvmLv, "lv_kernel_minor").is_valid) { m_major_device = lvm_lv_get_property(lvmLv, "lv_kernel_major").value.integer; m_minor_device = lvm_lv_get_property(lvmLv, "lv_kernel_minor").value.integer; } else { // some versions of lvm2app library don't return valid numbers getDeviceNumbers(m_major_device, m_minor_device); } m_persistent = (-1 != (static_cast(lvm_lv_get_property(lvmLv, "lv_major").value.integer))); QByteArray flags(lvm_lv_get_property(lvmLv, "lv_attr").value.string); m_writable = (flags[1] == 'w'); m_fixed = (flags[3] == 'm'); m_open = (flags[5] == 'o'); m_zero = (flags[7] == 'z'); m_partial = (flags[8] == 'p'); processSegments(lvmLv, flags); setSnapContainer(lvmVg, lvmLv); QString additional_state; switch (flags[0]) { case 'c': m_type = "under conversion"; m_lvmmirror = true; m_under_conversion = true; break; case 'e': m_type = "metadata"; m_metadata = true; if (flags[6] == 'r'){ m_type = m_segments[0]->type; m_raid = true; } break; case 'I': case 'i': if (flags[6] == 'r'){ if (m_lv_parent) { if (m_lv_parent->isRaid() && m_lv_parent->getRaidType() == 1) { m_type = "mirror leg"; m_raidmirror_leg = true; } else { m_type = "raid image"; } } else { m_type = "raid image"; } m_raid_image = true; } else { m_type = "mirror leg"; m_lvmmirror_leg = true; } if(flags[0] == 'I') { additional_state = "un-synced"; m_synced = false; } else { additional_state = "synced"; m_synced = true; } break; case 'L': case 'l': m_type = "mirror log"; m_lvmmirror_log = true; break; case 'M': // mirror logs can be mirrors themselves -- see below m_type = "lvm mirror"; m_lvmmirror = true; break; case 'm': m_type = "lvm mirror"; // Origin status overides mirror status in the flags if this is both m_lvmmirror = true; // We split it below -- snap_containers are origins and the lv is a mirror break; case 'O': m_type = "origin"; additional_state = "merging"; m_is_origin = true; m_merging = true; break; case 'o': m_type = "origin"; m_is_origin = true; break; case 'p': m_type = "pvmove"; m_pvmove = true; break; case 'r': m_type = m_segments[0]->type; m_raid = true; break; case 'R': m_type = m_segments[0]->type; m_raid = true; break; case 's': m_type = "snapshot"; m_cow_snap = true; break; case 'S': m_type = "snapshot"; additional_state = "merging"; m_cow_snap = true; m_merging = true; break; case 't': m_type = "thin pool"; m_thin_pool = true; break; case 'T': m_type = "thin data"; m_thin_data = true; break; case 'v': m_type = "virtual"; m_virtual = true; break; case 'V': m_type = "thin volume"; // For Non-thin snaps origin status overrides this in the flags -- we add it back below m_thin = true; break; default: m_type = "linear"; break; } if (m_lv_name.contains("_rmeta_")) { m_raid_metadata = true; m_metadata = true; } if (m_lv_name.contains("_tmeta_") || m_lv_name.endsWith("_tmeta")) { m_thin_metadata = true; m_metadata = true; } if ((flags[6] == 't') && m_is_origin) m_thin = true; if (m_lv_name.contains("_tdata_") || m_lv_name.endsWith("_tdata")) m_thin_data = true; lvm_property_value value; if (flags[6] == 't') { value = lvm_lv_get_property(lvmLv, "origin"); if (value.is_valid && (QString(value.value.string) != "")) { m_origin = value.value.string; m_cow_snap = false; m_thin_snap = true; m_thin = true; m_type = "thin snapshot"; } } setPolicy(flags[2]); m_active = false; switch (flags[4]) { case '-': m_state = "inactive"; break; case 'a': m_state = "active"; m_active = true; break; case 's': m_state = "suspended"; break; case 'I': m_state = "invalid"; m_valid = false; break; case 'S': m_state = "suspended"; break; case 'd': m_state = "no table"; break; case 'i': m_state = "inactive table"; break; default: m_state = "unknown"; } if (!additional_state.isEmpty()) m_state = additional_state + " / " + m_state; if (m_lv_name.contains("_mlog", Qt::CaseSensitive)) { m_lvmmirror_log = true; // this needs to be here in case it is a mirrored mirror log m_lv_fs = ""; } else if (m_lv_name.contains("_mimagetmp_", Qt::CaseSensitive)) { m_temp = true; m_lv_fs = ""; } else if (!m_lvmmirror_log && !isMirrorLeg() && !m_virtual && !m_metadata && !m_thin_pool) { m_lv_fs = fsprobe_getfstype2(m_lv_mapper_path); m_lv_fs_label = fsprobe_getfslabel(m_lv_mapper_path); m_lv_fs_uuid = fsprobe_getfsuuid(m_lv_mapper_path); } else { m_lv_fs = ""; m_lv_fs_label = ""; m_lv_fs_uuid = ""; } if (m_cow_snap || m_merging) { value = lvm_lv_get_property(lvmLv, "origin"); if (value.is_valid) m_origin = value.value.string; value = lvm_lv_get_property(lvmLv, "snap_percent"); if (value.is_valid) m_snap_percent = lvm_percent_to_float(value.value.integer); else m_snap_percent = 0; } else if (m_thin || m_thin_pool) { // Calling up snap_percent on thin snaps causes segfaults value = lvm_lv_get_property(lvmLv, "data_percent"); if (value.is_valid) m_data_percent = lvm_percent_to_float(value.value.integer); } else { m_origin = ""; } if ((m_lvmmirror_leg || m_lvmmirror_log)) { if (m_lvmmirror_log && m_lvmmirror) m_type = QString("log mirror"); if (m_lvmmirror_log && m_lvmmirror_leg) m_type = QString("log image"); else if ((m_lvmmirror || m_virtual) && !m_lvmmirror_log) m_lvmmirror_leg = true; } value = lvm_lv_get_property(lvmLv, "copy_percent"); if (value.is_valid) m_copy_percent = lvm_percent_to_float(value.value.integer); else m_copy_percent = 0; if (m_snap_container && !was_snap_container) m_uuid = QUuid::createUuid().toString(); else if (!m_snap_container) m_uuid = lvm_lv_get_property(lvmLv, "lv_uuid").value.string; processMounts(); if (m_snap_container) { m_type = "origin"; if(flags.size() > 6){ if (flags[6] == 'r') m_raid = true; else if (flags[6] == 't') m_thin = true; } } else if (m_type.contains("origin", Qt::CaseInsensitive) && !m_snap_container) { if(flags.size() > 6){ if (flags[6] == 'r') m_raid = true; else if (flags[6] == 't') m_thin = true; } if (m_thin) m_type = "thin volume"; else m_type = m_segments[0]->type; } insertChildren(lvmLv, lvmVg); countLegsAndLogs(); calculateTotalSize(); } void LogVol::setPolicy(const char flag2) { m_alloc_locked = false; switch (flag2) { case 'C': m_alloc_locked = true; case 'c': m_policy = CONTIGUOUS; break; case 'L': m_alloc_locked = true; case 'l': m_policy = CLING; break; case 'N': m_alloc_locked = true; case 'n': m_policy = NORMAL; break; case 'A': m_alloc_locked = true; case 'a': m_policy = ANYWHERE; break; case 'I': m_alloc_locked = true; case 'i': if (m_vg->getPolicy() == CLING) m_policy = INHERIT_CLING; else if (m_vg->getPolicy() == ANYWHERE) m_policy = INHERIT_ANYWHERE; else if (m_vg->getPolicy() == CONTIGUOUS) m_policy = INHERIT_CONTIGUOUS; else m_policy = INHERIT_NORMAL; break; default: m_policy = NORMAL; break; } } void LogVol::setSnapContainer(vg_t lvmVg, lv_t lvmLv) { QList lvm_child_snapshots(getLvmSnapshots(lvmVg)); if ((!lvm_child_snapshots.isEmpty()) && m_lv_parent == nullptr) { m_snap_container = true; m_seg_total = 1; } else if ((!lvm_child_snapshots.isEmpty()) && m_lv_parent != nullptr) { if (m_lv_parent->isThinPool()) { m_snap_container = true; m_seg_total = 1; } else if (m_lv_parent->getFullName() != getFullName() ) { m_snap_container = true; m_seg_total = 1; } else { m_snap_container = false; m_seg_total = lvm_lv_get_property(lvmLv, "seg_count").value.integer; } } else { m_snap_container = false; m_seg_total = lvm_lv_get_property(lvmLv, "seg_count").value.integer; } } void LogVol::processMounts() { m_mount_entries = m_tables->getMtabEntries(m_major_device, m_minor_device); QStringList mpts; for (auto entry : m_mount_entries) mpts << entry->getMountPoint(); m_mounted = !mpts.isEmpty(); if (m_mounted) { const FSData fs_data = get_fs_data(mpts[0]); if (fs_data.size > 0) { m_fs_size = fs_data.size * fs_data.block_size; m_fs_used = fs_data.used * fs_data.block_size; } else { m_fs_size = -1; m_fs_used = -1; } } else { m_fs_size = -1; m_fs_used = -1; } } void LogVol::insertChildren(lv_t lvmLv, vg_t lvmVg) { lv_t lvm_child; QList lvm_child_snapshots(getLvmSnapshots(lvmVg)); m_lv_children.clear(); if (m_snap_container) { for (const auto snap : lvm_child_snapshots) m_lv_children << SmrtLvPtr(new LogVol(snap, lvmVg, m_vg, this, m_tables)); m_lv_children << SmrtLvPtr(new LogVol(lvmLv, lvmVg, m_vg, this, m_tables)); } else { QStringList names; names << removePvNames() << getMetadataNames() << getPoolVolumeNames(lvmVg); if (m_lvmmirror && !m_log.isEmpty()) names << m_log; names.removeDuplicates(); for (const auto name : names) { QByteArray qba = name.toLocal8Bit(); lvm_child = lvm_lv_from_name(lvmVg, qba.data()); if (lvm_child) { QByteArray flags(lvm_lv_get_property(lvm_child, "lv_attr").value.string); if (!flags.isEmpty() && flags[0] !='-') // filters out normal volumes which are never children m_lv_children << SmrtLvPtr(new LogVol(lvm_child, lvmVg, m_vg, this, m_tables)); } } } } void LogVol::countLegsAndLogs() { m_mirror_count = 0; m_log_count = 0; if (m_lvmmirror) { for (const auto lv : getAllChildrenFlat()) { if (lv->isLvmMirrorLeg() && !lv->isMirror() && !lv->isLvmMirrorLog()) m_mirror_count++; if (lv->isLvmMirrorLog() && !lv->isMirror()) m_log_count++; } } else if (getRaidType() == 1) { for (const auto lv : getAllChildrenFlat()) { if (lv->isRaidImage()) m_mirror_count++; } } else { m_mirror_count = 1; // linear volumes count as mirror = 1; } } QList LogVol::getLvmSnapshots(vg_t lvmVg) { lvm_property_value value; dm_list *lv_dm_list = lvm_vg_list_lvs(lvmVg); lvm_lv_list *lv_list; QList lvm_snapshots; if (lv_dm_list) { dm_list_iterate_items(lv_list, lv_dm_list) { value = lvm_lv_get_property(lv_list->lv, "origin"); if (QString(value.value.string).trimmed() == m_lv_name) lvm_snapshots << lv_list->lv; } } return lvm_snapshots; } QStringList LogVol::getPoolVolumeNames(vg_t lvmVg) // Excluding snapshots -- they go under the origins { lvm_property_value value; dm_list *const lv_dm_list = lvm_vg_list_lvs(lvmVg); lvm_lv_list *lv_list; QStringList names; if (lv_dm_list) { dm_list_iterate_items(lv_list, lv_dm_list) { value = lvm_lv_get_property(lv_list->lv, "origin"); if (value.is_valid) { if (QString(value.value.string) == "") { value = lvm_lv_get_property(lv_list->lv, "pool_lv"); if (value.is_valid) { if (QString(value.value.string).trimmed() == m_lv_name) { value = lvm_lv_get_property(lv_list->lv, "lv_name"); if (value.is_valid) names << QString(value.value.string).trimmed(); } } } } } } return names; } // Finds logical volumes that are children of this volume by // removing physical volumes from the list along with pvmove // volumes. One pvmove can be under several lvs so isn't // really a child. QStringList LogVol::removePvNames() { QStringList names(getPvNamesAll()); for (int i = names.size() - 1; i >= 0; i--) { if (names[i].startsWith("pvmove")) names.removeAt(i); } for (const auto pv : m_vg->getPhysicalVolumes()) { for (int i = names.size() - 1; i >= 0; i--) { if (pv->getMapperName() == names[i]) names.removeAt(i); } } return names; } // Finds metadata child sub volumes of this volume // TODO -- ophans can be named this way too. // Rewrite to filter them rather than doing it in // insertChildren() QStringList LogVol::getMetadataNames() { QStringList children; for (const auto lv : m_vg->getLvNamesAll()) { if (lv.contains("_rmeta_")) { if (m_lv_name == lv.left(lv.indexOf("_rmeta_"))) children << lv; } else if (lv.endsWith("_tmeta") && m_thin_pool) { if (m_lv_name == lv.left(lv.indexOf("_tmeta"))) children << lv; } } return children; } void LogVol::calculateTotalSize() { m_total_size = 0; if (!m_thin_pool && m_lv_children.size()) { for (const auto &child : m_lv_children) m_total_size += child->getTotalSize(); } else if (m_thin_pool) { for (const auto &child : m_lv_children) { if (child->isThinPoolData() || child->isMetadata()) m_total_size += child->getTotalSize(); } } else { m_total_size = m_size; } } void LogVol::processSegments(lv_t lvmLv, QByteArray flags) { lvm_property_value value; dm_list* lvseg_dm_list = lvm_lv_list_lvsegs(lvmLv); lvm_lvseg_list *lvseg_list; lvseg_t lvm_lvseg; while (m_segments.size()) delete m_segments.takeAt(0); if (lvseg_dm_list) { dm_list_iterate_items(lvseg_list, lvseg_dm_list) { lvm_lvseg = lvseg_list->lvseg; Segment *segment = new Segment(); value = lvm_lvseg_get_property(lvm_lvseg, "segtype"); if (value.is_valid) segment->type = value.value.string; if (segment->type == QString("mirror")) { m_lvmmirror = true; segment->type = QString("lvm mirror"); } else if (segment->type == QString("raid1")) { m_raidmirror = true; segment->type = QString("raid1 mirror"); } const bool pvmove = (flags[0] == 'p'); // use m_pvmove if processSegments is moved to after the main section if ((m_lvmmirror || m_raidmirror) && !pvmove) { segment->stripes = 1; segment->stripe_size = 1; segment->size = 1; } else if (pvmove) { segment->stripes = 1; segment->stripe_size = 1; value = lvm_lvseg_get_property(lvm_lvseg, "seg_size"); if (value.is_valid) segment->size = value.value.integer; } else { value = lvm_lvseg_get_property(lvm_lvseg, "stripes"); if (value.is_valid) segment->stripes = value.value.integer; if (MasterList::isLvmVersionEqualOrGreater("2.02.100")) { // stripe_size bug fixed if (MasterList::isLvmVersionEqualOrGreater("2.02.164")) { value = lvm_lvseg_get_property(lvm_lvseg, "stripe_size"); if (value.is_valid) segment->stripe_size = value.value.integer; } else { value = lvm_lvseg_get_property(lvm_lvseg, "stripesize"); if (value.is_valid) segment->stripe_size = value.value.integer; } } else { value = lvm_lvseg_get_property(lvm_lvseg, "stripesize"); if (value.is_valid) segment->stripe_size = value.value.integer * 512; } value = lvm_lvseg_get_property(lvm_lvseg, "seg_size"); if (value.is_valid) segment->size = value.value.integer; } QString raw_paths; QStringList devices_and_starts; value = lvm_lvseg_get_property(lvm_lvseg, "devices"); if (value.is_valid) raw_paths = value.value.string; if (raw_paths.size()) { for (auto dev : raw_paths.split(',')) { QStringList temp = dev.split('('); segment->device_path.append(findMapperPath(temp[0])); segment->starting_extent.append((temp[1].remove(')')).toLongLong()); } } if (flags[0] == 't') { value = lvm_lvseg_get_property(lvm_lvseg, "chunksize"); if (value.is_valid) { if (MasterList::isLvmVersionEqualOrGreater("2.02.100")) // chunksize bug fixed in v2.2.100 segment->chunk_size = value.value.integer; else segment->chunk_size = value.value.integer * 512; } else { segment->chunk_size = 0; } } else { segment->chunk_size = 0; } if (MasterList::isLvmVersionEqualOrGreater("2.02.109")) { // discards bug fixed by v2.2.109 if (flags[0] == 't') { value = lvm_lvseg_get_property(lvm_lvseg, "discards"); if (value.is_valid) segment->discards = value.value.string; } } m_segments.append(segment); } } } LvList LogVol::getAllChildrenFlat() const { LvList flat_list = getChildren(); for (const auto child : m_lv_children) flat_list << child->getAllChildrenFlat(); return flat_list; } LvList LogVol::getChildren() const { LvList children; for (auto child : m_lv_children) { children << child.data(); } return children; } LvList LogVol::getSnapshots() const { LvList snapshots; const LogVol *cntr = this; if (cntr->getParent() != nullptr && !cntr->isSnapContainer()) { if (cntr->getFullName() == cntr->getParent()->getFullName()) cntr = cntr->getParent(); } if (cntr->isSnapContainer()) { snapshots = cntr->getChildren(); for (int i = snapshots.size() - 1; i >= 0; i--) { // delete the 'real' lv leaving the snaps if (m_lv_name == snapshots[i]->getName()) snapshots.removeAt(i); } } return snapshots; } LvList LogVol::getThinVolumes() const // not including snap containers { LvList vols; if (m_thin_pool) { for (const auto lv : getAllChildrenFlat()) { if (lv->isThinVolume() && !lv->isSnapContainer()) vols << lv; } } return vols; } LvList LogVol::getThinDataVolumes() const { LvList data; if (m_thin_pool) { for (const auto lv : getChildren()) { if (lv->isThinPoolData()) data << lv; } } return data; } LvList LogVol::getThinMetadataVolumes() const { LvList meta; if (m_thin_pool) { for (const auto lv : getChildren()) { if (lv->isThinMetadata()) meta << lv; } } return meta; } LvList LogVol::getRaidImageVolumes() const { LvList images; if (m_raid) { for (const auto lv : getAllChildrenFlat()) { if (lv->isRaidImage()) images << lv; } } return images; } LvList LogVol::getRaidMetadataVolumes() const { LvList meta; if (m_raid) { for (const auto lv : getAllChildrenFlat()) { if (lv->isRaidMetadata()) meta << lv; } } return meta; } // Returns the mirror than owns this mirror leg or mirror log. Returns // nullptr if this is not part of a mirror volume. LogVol * LogVol::getParentMirror() { LogVol *mirror = this; if (isLvmMirrorLog() || isLvmMirrorLeg() || isTemporary()) { if (isLvmMirrorLog() && isLvmMirrorLeg() && mirror->getParent()) // mirrored mirror log mirror = getParent()->getParent(); else if (isLvmMirrorLog() || isLvmMirrorLeg()) mirror = getParent(); if (mirror && mirror->isTemporary()) // under conversion temp mirror mirror = mirror->getParent(); } else if (isMirrorLeg()) { // it's raid mirror = getParent(); } else { mirror = nullptr; } return mirror; } // Returns the RAID volume than owns this RAID component // nullptr if this is not part of a RAID volume. LogVol * LogVol::getParentRaid() { if (m_raid_metadata || m_raid_image) return getParent(); else return nullptr; } QStringList LogVol::getPvNamesAll() const { QStringList pv_names; for (const auto seg : m_segments) pv_names << seg->device_path; pv_names.sort(); pv_names.removeDuplicates(); return pv_names; } QStringList LogVol::getPvNamesAllFlat() const { QStringList pv_names; if (isRaidMetadata()) { pv_names << getPvNamesAll(); } else if (m_snap_container || m_lvmmirror || m_raid) { for (auto child : getChildren()) pv_names << child->getPvNamesAllFlat(); } else if (m_thin_pool) { for (auto child : getChildren()) { if (child->isThinPoolData() || child->isThinMetadata()) pv_names << child->getPvNamesAllFlat(); } } else { pv_names << getPvNamesAll(); } pv_names.sort(); pv_names.removeDuplicates(); return pv_names; } long long LogVol::getSpaceUsedOnPv(const QString pvname) const { long long space_used = 0; if (m_thin_pool) { for (auto data : getThinDataVolumes()) space_used += data->getSpaceUsedOnPv(pvname); for (auto meta : getThinMetadataVolumes()) space_used += meta->getSpaceUsedOnPv(pvname); } else if (m_raid && !(m_raid_metadata || m_raid_image)) { for (auto image : getRaidImageVolumes()) space_used += image->getSpaceUsedOnPv(pvname); for (auto meta : getRaidMetadataVolumes()) space_used += meta->getSpaceUsedOnPv(pvname); } else { for (auto seg : m_segments) { for (auto path : seg->device_path) { if (path == pvname) space_used += (seg->size) / (seg->stripes) ; } } } return space_used; } long long LogVol::getMissingSpace() const { LvList const children = getChildren(); long long missing = 0; if (isPartial()) { if (children.isEmpty()) { for (const auto pvname : getPvNamesAllFlat()) { PhysVol *const pv = m_vg->getPvByName(pvname); if (pv && !pv->isMissing()) missing -= getSpaceUsedOnPv(pvname); } } else { missing = 0; for (auto child : children) missing += child->getMissingSpace(); } } else { missing = 0; } return missing; } long long LogVol::getChunkSize(int segment) const { if (segment > m_segments.size() - 1) segment = 0; return m_segments[segment]->chunk_size; } int LogVol::getRaidType() const { int type; QRegExp reg("[0-9]+"); QStringList matches; // This needs to be fixed so changes in the string // m_type can't screw with it. if (m_raid && reg.indexIn(m_type) >= 0) { matches = reg.capturedTexts(); type = matches[0].toInt(); } else { type = -1; } return type; } QString LogVol::getDiscards(int segment) const { if (segment > m_segments.size() - 1) segment = 0; return m_segments[segment]->discards; } double LogVol::getSnapPercent() const { if (m_cow_snap || m_merging) { if (m_active) return m_snap_percent; else return -1; } else { return 0.0; } } double LogVol::getCopyPercent() const { if (m_active) return m_copy_percent; else return -1; } double LogVol::getDataPercent() const { if (m_active) return m_data_percent; else return -1; } bool LogVol::isSynced() const { if (m_synced) { for (auto child : getChildren()) { if (!child->isSynced()) return false; } return true; } else { return false; } } int LogVol::getSegmentStripes(const int segment) { return m_segments[segment]->stripes; } int LogVol::getSegmentStripeSize(const int segment) { return m_segments[segment]->stripe_size; } long long LogVol::getSegmentSize(const int segment) { return m_segments[segment]->size; } long long LogVol::getSegmentExtents(const int segment) { return (m_segments[segment]->size / m_vg->getExtentSize()); } QList LogVol::getSegmentStartingExtent(const int segment) { return m_segments[segment]->starting_extent; } QStringList LogVol::getPvNames(const int segment) const { return m_segments[segment]->device_path; } // Returns the raid metadata volume associated with a raid image volume LogVol * LogVol::getRaidImageMetadata() const { if (isRaidImage()) { return m_vg->getLvByName(getName().replace(QString("_rimage_"), QString("_rmeta_"))); } else { return nullptr; } } // Returns the raid image volume associated with a raid metadata volume LogVol * LogVol::getRaidMetadataImage() const { if (isRaidMetadata()) { return m_vg->getLvByName(getName().replace(QString("_rmeta_"), QString("_rimage_"))); } else { return nullptr; } } QStringList LogVol::getMountPoints() const { QStringList mpts; for (auto entry : m_mount_entries) mpts << entry->getMountPoint(); return mpts; } void LogVol::getDeviceNumbers(unsigned long &majornum, unsigned long &minornum) { majornum = 0; minornum = 0; QFileInfo fi(m_lv_mapper_path); if (!fi.exists()) return; QByteArray qba = fi.canonicalFilePath().toLocal8Bit(); struct stat fs; if (stat(qba.data(), &fs)) // error return; majornum = major(fs.st_rdev); minornum = minor(fs.st_rdev); } kvpm-0.9.10/kvpm/externalraid.cpp0000644000175000017500000001104512770324126017152 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "externalraid.h" #include #include #include #include QStringList dmraid_get_raid(dm_tree_node *const parent, dm_tree_node *const root); QStringList dmraid_get_block(dm_tree_node *const parent, dm_tree_node *const root); void dmraid_get_devices(QStringList &block, QStringList &raid) { dm_lib_init(); dm_log_with_errno_init(nullptr); dm_task *const dmt_list = dm_task_create(DM_DEVICE_LIST); dm_task *const dmt_info = dm_task_create(DM_DEVICE_INFO); dm_tree *const tree = dm_tree_create(); dm_task_run(dmt_list); dm_names *list_ptr = dm_task_get_names(dmt_list); if(list_ptr->dev) { // first dev == 0 for empty list int next = 0; dm_info dmi; do { if (dm_task_set_name(dmt_info, list_ptr->name)) { if (dm_task_run(dmt_info)) { if (dm_task_get_info(dmt_info, &dmi)) { dm_tree_add_dev(tree, dmi.major, dmi.minor); next = list_ptr->next; list_ptr = (dm_names *)(next + (char *)list_ptr); // add offset "next" bytes to pointer } } } } while (next); } dm_tree_node *const root = dm_tree_find_node(tree, 0, 0); raid << dmraid_get_raid(root, root); block << dmraid_get_block(root, root); raid.removeDuplicates(); block.removeDuplicates(); dm_tree_free(tree); dm_task_destroy(dmt_list); dm_task_destroy(dmt_info); dm_lib_release(); } QStringList dmraid_get_raid(dm_tree_node *const parent, dm_tree_node *const root) { QStringList raid; void *handle = nullptr; const QRegExp rxp("^-[0-9]+$"); const QString parent_uuid(dm_tree_node_get_uuid(parent)); while(dm_tree_node *node = dm_tree_next_child(&handle, parent, 0)) { if(node == root) break; QString uuid(dm_tree_node_get_uuid(node)); if (uuid.startsWith("DMRAID-")) { if(!uuid.remove(parent_uuid).contains(rxp)) { raid << QString("/dev/mapper/").append(dm_tree_node_get_name(node)); raid << dmraid_get_raid(node, root); } } else if (uuid.startsWith("LVM-")) { raid << dmraid_get_raid(node, root); } else if (uuid.isEmpty()) { // might be a partition raid << dmraid_get_raid(node, root); // look for and underlying device } } return raid; } QStringList dmraid_get_block(dm_tree_node *const parent, dm_tree_node *const root) { QStringList block; const int buff_size = 1000; char dev_name[buff_size]; void *handle = nullptr; while(dm_tree_node *node = dm_tree_next_child(&handle, parent, 0)) { if(node == root) break; const QString uuid(dm_tree_node_get_uuid(node)); if (uuid.startsWith("DMRAID-") || uuid.startsWith("LVM-")) { block << dmraid_get_block(node, root); } else if (QString(dm_tree_node_get_uuid(parent)).startsWith("DMRAID-") && uuid.isEmpty()) { const dm_info *const dmi = dm_tree_node_get_info(node); dm_device_get_name(dmi->major, dmi->minor, 1, dev_name, buff_size); block << QString("/dev/").append(dev_name); } else { block << dmraid_get_block(node, root); } } return block; } void mdraid_get_devices(QStringList &block, QStringList &raid) { QFile mdstat("/proc/mdstat"); if(mdstat.exists() && mdstat.open(QIODevice::ReadOnly)) { QTextStream stream(&mdstat); QString line; const QRegExp raid_rx("^md[0-9]+ :"); const QRegExp bracket_rx("\\[[0-9]+\\]$"); do { line = stream.readLine(); if(line.contains(raid_rx)) { QStringList split = line.split(' '); raid << QString("/dev/").append(split[0]); for(int x = 4; x < split.size(); ++x) block << QString("/dev/").append(split[x].remove(bracket_rx)); } } while (!line.isNull()); } raid.removeDuplicates(); block.removeDuplicates(); return; } kvpm-0.9.10/kvpm/mountentry.cpp0000644000175000017500000000517712770324126016725 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "mountentry.h" #include #include #include #include "logvol.h" #include "volgroup.h" // MountEntry provides a thin wrapper for 'mntent' structs by providing // QStrings rather than const char arrays and a 'mount position' property MountEntry::MountEntry(MountEntry *const copy, QObject *parent) : QObject(parent) { m_device_name = copy->getDeviceName(); m_mount_point = copy->getMountPoint(); m_filesystem_type = copy->getFilesystemType(); m_mount_options = copy->getMountOptions(); m_major = copy->getMajorNumber(); m_minor = copy->getMinorNumber(); m_mount_position = copy->getMountPosition(); } MountEntry::MountEntry(mntent *const entry, QObject *parent) : QObject(parent) { m_device_name = QString(entry->mnt_fsname).trimmed(); m_mount_point = QString(entry->mnt_dir).trimmed(); m_filesystem_type = QString(entry->mnt_type).trimmed(); m_mount_options = QString(entry->mnt_opts).trimmed(); m_major = -1; m_minor = -1; m_mount_position = 0; } MountEntry::MountEntry(const QString mountinfo, const int major, const int minor, QObject *parent) : QObject(parent) { m_mount_point = mountinfo.section(' ', 4, 4).replace("\\040", " "); m_mount_options = mountinfo.section(' ', 5, 5); m_filesystem_type = mountinfo.section(' ', 6, 6); m_device_name = mountinfo.section(' ', 8, 8); m_super_options = mountinfo.section(' ', 9, 9); m_major = major; m_minor = minor; m_mount_position = 0; } MountEntry::~MountEntry() { } QString MountEntry::getDeviceName() { return m_device_name; } QString MountEntry::getMountPoint() { return m_mount_point; } QString MountEntry::getFilesystemType() { return m_filesystem_type; } QString MountEntry::getMountOptions() { QStringList options; options.append(m_mount_options.split(',')); options.append(m_super_options.split(',')); options.removeDuplicates(); return options.join(","); } int MountEntry::getMountPosition() { return m_mount_position; } int MountEntry::getMajorNumber() { return m_major; } int MountEntry::getMinorNumber() { return m_minor; } void MountEntry::setMountPosition(const int pos) { m_mount_position = pos; } kvpm-0.9.10/kvpm/partbase.h0000644000175000017500000000533712770324126015745 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PARTBASE_H #define PARTBASE_H #include #include #include class QLabel; class QString; class QVBoxLayout; class PartitionGraphic; class DualSelectorBox; class StoragePartition; class PartitionDialogBase : public KDialog { Q_OBJECT StoragePartition *m_old_storage_part; PedPartition *m_existing_part; // The partition on the disk now QVBoxLayout *m_layout; bool m_use_si_units; bool m_is_new; // we are creating a new partition, not changing an existing one bool m_bailout; PedSector m_min_shrink_size; // Minimum size of the fs after shrinking -- in sectors PedSector m_sector_size; // bytes per logical sector PedSector m_max_start; // start of biggest possible partition PedSector m_max_end; // end of largest possible partition PedSector m_current_start; PedSector m_current_size; QString m_path; // the path to the partition under /dev PartitionGraphic *m_display_graphic; // The color bar that shows the relative // size of the partition graphically QLabel *m_change_by_label, // How much are we growing or shrinking the partition? *m_move_by_label, // How much are we moving the partition? *m_preceding_label, // Free space before the proposed partition *m_following_label; DualSelectorBox *m_dual_selector; bool setMaxFreespace(PedSector &start, PedSector &end); void setMaxPart(PedSector &start, PedSector &end); PedSector setMinSize(); void buildDialog(); signals: void changed(); protected: void updateGraphicAndLabels(); bool isValid(); bool hasInitialErrors(); QString getPath(); PedPartition *getPedPartition(); bool pedCommitAndWait(PedDisk *const disk); PedSector getSectorSize(); void insertWidget(QWidget *const widget); // The following are all in *sectors* -- just in case that isn't obvious PedSector getMaxSize(); PedSector getMaxStart(); PedSector getMaxEnd(); PedSector getNewOffset(); PedSector getMinSize(); PedSector getNewSize(); PedSector getCurrentStart(); PedSector getCurrentSize(); public: explicit PartitionDialogBase(StoragePartition *const partition, QWidget *parent = NULL); virtual ~PartitionDialogBase() {} }; #endif kvpm-0.9.10/kvpm/vgwarning.cpp0000644000175000017500000000612312770324126016473 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgwarning.h" #include #include #include #include #include #include #include "volgroup.h" VGWarning::VGWarning(QWidget *parent) : QWidget(parent) { m_layout = new QVBoxLayout(); setLayout(m_layout); hide(); } void VGWarning::loadMessage(VolGroup *const group) { for (auto child : this->findChildren()) { m_layout->removeWidget(child); child->deleteLater(); } if (group->isExported() || group->isPartial() || group->openFailed()) { show(); if (group->isExported()) m_layout->addWidget(buildExportedNotice()); if (group->isPartial()) m_layout->addWidget(buildPartialWarning()); if (group->openFailed()) m_layout->addWidget(buildOpenFailedWarning(group)); } else { hide(); } } QWidget *VGWarning::buildExportedNotice() { QWidget *const notice = new QWidget(); QHBoxLayout *const notice_layout = new QHBoxLayout(); notice->setLayout(notice_layout); notice_layout->addStretch(); notice_layout->addWidget(new QLabel(i18n("Exported Volume Group"))); notice_layout->addStretch(); return notice; } QWidget *VGWarning::buildOpenFailedWarning(VolGroup *const group) { QWidget *const warning = new QWidget(); QHBoxLayout *const warning_layout = new QHBoxLayout(); warning->setLayout(warning_layout); warning_layout->addStretch(); QLabel *const icon_label = new QLabel(); icon_label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(32, 32)); warning_layout->addWidget(icon_label); warning_layout->addSpacing(10); QLabel *warning_label = new QLabel(); if (group->isClustered()) warning_label->setText(i18n("Warning: clustered volume group could not be opened")); else warning_label->setText(i18n("Warning: volume group could not be opened")); warning_layout->addWidget(warning_label); warning_layout->addStretch(); return warning; } QWidget *VGWarning::buildPartialWarning() { QWidget *const warning = new QWidget(); QHBoxLayout *const warning_layout = new QHBoxLayout(); warning->setLayout(warning_layout); warning_layout->addStretch(); QLabel *const icon_label = new QLabel(); icon_label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(32, 32)); warning_layout->addWidget(icon_label); warning_layout->addSpacing(10); QLabel *const warning_label = new QLabel(i18n("Warning: Partial volume group, some physical volumes are missing")); warning_layout->addWidget(warning_label); warning_layout->addStretch(); return warning; } kvpm-0.9.10/kvpm/thincreate.cpp0000644000175000017500000002402112770324126016614 0ustar benscottbenscott/* * * * Copyright (C) 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "thincreate.h" #include "fsextend.h" #include "logvol.h" #include "misc.h" #include "mountentry.h" #include "processprogress.h" #include "volgroup.h" #include #include #include /* This class handles both the creation and extention of thin volumes or snapshots since the processes are so similar. */ /* Create new thin volume*/ ThinCreateDialog::ThinCreateDialog(LogVol *const pool, QWidget *parent): LvCreateDialogBase(pool->getVg(), -1, false, false, true, false, QString(""), pool->getName(), parent), m_pool(pool) { m_lv = nullptr; m_extend = false; m_snapshot = false; m_fs_can_extend = false; const long long extent_size = getVg()->getExtentSize(); const long long max_size = getLargestVolume(); setCaption("Create A New Thin Volume"); initializeSizeSelector(extent_size, 0, max_size / extent_size); setInfoLabels(THIN, 0, 0, max_size); connect(this, SIGNAL(extendFs()), this, SLOT(setMaxSize())); setMaxSize(); resetOkButton(); if (hasInitialErrors()) preventExec(); } /* extend thin volume or take snapshot */ ThinCreateDialog::ThinCreateDialog(LogVol *const volume, const bool snapshot, QWidget *parent): LvCreateDialogBase(volume->getVg(), fs_max_extend(volume->getMapperPath(), volume->getFilesystem(), volume->isMounted()), !snapshot, snapshot, true, false, volume->getName(), volume->getPoolName(), parent), m_snapshot(snapshot), m_extend(!snapshot), m_lv(volume) { const long long extent_size = getVg()->getExtentSize(); const long long max_size = getLargestVolume(); if (!snapshot) { setCaption("Extend Thin Volume"); initializeSizeSelector(extent_size, m_lv->getExtents(), max_size / extent_size); } else { setCaption("Create Thin Snapshot"); } setInfoLabels(THIN, 0, 0, max_size); connect(this, SIGNAL(extendFs()), this, SLOT(setMaxSize())); setMaxSize(); resetOkButton(); if (hasInitialErrors()) preventExec(); } void ThinCreateDialog::setMaxSize() { const long long max = getLargestVolume() / getVg()->getExtentSize(); const long long maxfs = getMaxFsSize() / getVg()->getExtentSize(); if (getExtendFs()) { if (max < maxfs) setSelectorMaxExtents(max); else setSelectorMaxExtents(maxfs); } else { setSelectorMaxExtents(max); } resetOkButton(); } void ThinCreateDialog::resetOkButton() { enableButtonOk(isValid()); } long long ThinCreateDialog::getLargestVolume() { // Current limitation of number of extents is 32bit unsigned return 0xFFFFFFFF * getVg()->getExtentSize(); } /* Here we create a stringlist of arguments based on all the options that the user chose in the dialog. */ QStringList ThinCreateDialog::args() { QString program_to_run; QStringList args; if (!getUdev()) args << "--noudevsync"; if (!m_snapshot) { if (m_extend) args << "--size"; else args << "--virtualsize"; args << QString("%1b").arg(getSelectorExtents() * getVg()->getExtentSize()); } if (!m_extend) { if (!getName().isEmpty()) args << "--name" << getName(); if (!getTag().isEmpty()) args << "--addtag" << getTag(); if (getReadOnly()) args << "--permission" << "r" ; else args << "--permission" << "rw" ; if (getPersistent()) { args << "--persistent" << "y"; args << "--major" << getMajor(); args << "--minor" << getMinor(); } } if (!m_extend && !m_snapshot) { // create a thin volume program_to_run = "lvcreate"; args << "--thin" << m_pool->getFullName(); } else if (m_snapshot) { // create a thin snapshot program_to_run = "lvcreate"; args << "--snapshot" << m_lv->getFullName(); } else { // extend the current volume program_to_run = "lvextend"; args << m_lv->getFullName(); } args.prepend(program_to_run); return args; } // This function checks for problems that would make showing this dialog pointless // returns true if there are problems. bool ThinCreateDialog::hasInitialErrors() { if (getVg()->isPartial()) { if (m_extend) KMessageBox::sorry(this, i18n("Volumes can not be extended while physical volumes are missing")); else KMessageBox::sorry(this, i18n("Volumes can not be created while physical volumes are missing")); return true; } if (m_extend) { const QString warning1 = i18n("If this volume has a filesystem or data, it will need to be extended later " "by an appropriate tool. \n \n" "Currently, only the ext2, ext3, ext4, xfs, jfs, ntfs and reiserfs file systems are " "supported for extension. "); const QString warning2 = i18n("This filesystem seems to be as large as it can get, it will not be extended with the volume"); const QString warning3 = i18n("ntfs cannot be extended while mounted. The filesystem will need to be " "extended later or unmounted before the volume is extended."); if (m_lv->isCowOrigin()) { if (m_lv->isOpen()) { KMessageBox::sorry(this, i18n("Snapshot origins cannot be extended while open or mounted")); return true; } const LvList snap_shots = m_lv->getSnapshots(); for (int x = 0; x < snap_shots.size(); x++) { if (snap_shots[x]->isOpen() && !snap_shots[x]->isThinVolume()) { KMessageBox::sorry(this, i18n("Volumes cannot be extended with open or mounted snapshots")); return true; } } } m_fs_can_extend = fs_can_extend(m_lv->getFilesystem(), m_lv->isMounted()); const long long maxfs = getMaxFsSize() / m_lv->getVg()->getExtentSize(); const long long current = m_lv->getExtents(); if ((m_lv->getFilesystem() == "ntfs") && m_lv->isMounted()) { if (KMessageBox::warningContinueCancel(nullptr, warning3, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return true; } } else if (!(m_fs_can_extend || m_lv->isCowSnap())) { if (KMessageBox::warningContinueCancel(nullptr, warning1, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return true; } } else if (current >= maxfs) { if (KMessageBox::warningContinueCancel(nullptr, warning2, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return true; } } } return false; } void ThinCreateDialog::commit() { QStringList lvchange_args; hide(); if (!m_extend) { ProcessProgress create_lv(args()); return; } else { const QString mapper_path = m_lv->getMapperPath(); const QString fs = m_lv->getFilesystem(); if (m_lv->isCowOrigin()) { lvchange_args << "lvchange" << "-an" << mapper_path; ProcessProgress deactivate_lv(lvchange_args); if (deactivate_lv.exitCode()) { KMessageBox::error(0, i18n("Volume deactivation failed, volume not extended")); return; } ProcessProgress extend_origin(args()); if (extend_origin.exitCode()) { KMessageBox::error(0, i18n("Volume extension failed")); return; } lvchange_args.clear(); lvchange_args << "lvchange" << "-ay" << mapper_path; ProcessProgress activate_lv(lvchange_args); if (activate_lv.exitCode()) { if (getExtendFs()) KMessageBox::error(0, i18n("Volume activation failed, filesystem not extended")); else KMessageBox::error(0, i18n("Volume activation failed")); } else if (getExtendFs()) { fs_extend(m_lv->getMapperPath(), fs, m_lv->getMountPoints(), true); } return; } else { ProcessProgress extend_lv(args()); if (!extend_lv.exitCode() && getExtendFs()) fs_extend(mapper_path, fs, m_lv->getMountPoints(), true); return; } } } kvpm-0.9.10/kvpm/partflag.h0000644000175000017500000000257012770324126015740 0ustar benscottbenscott/* * * * Copyright (C) 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PARTFLAG_H #define PARTFLAG_H #include #include class QAbstractButton; class QButtonGroup; class QCheckBox; class StoragePartition; class PartitionFlagDialog : public KDialog { Q_OBJECT QButtonGroup *m_bg; QCheckBox *m_legacy_boot_check; QCheckBox *m_boot_check; QCheckBox *m_hidden_check; QCheckBox *m_raid_check; QCheckBox *m_lvm_check; QCheckBox *m_lba_check; QCheckBox *m_hp_service_check; QCheckBox *m_palo_check; QCheckBox *m_prep_check; QCheckBox *m_msftres_check; QCheckBox *m_bios_grub_check; QCheckBox *m_atvrecv_check; QCheckBox *m_diag_check; QCheckBox *m_root_check; QCheckBox *m_swap_check; StoragePartition *m_storage_part; bool m_bailout; private slots: void commit(); void setChecks(); void makeExclusive(QAbstractButton *button); public: explicit PartitionFlagDialog(StoragePartition *const partition, QWidget *parent = NULL); bool bailout(); }; #endif kvpm-0.9.10/kvpm/lvrename.h0000644000175000017500000000165412770324126015753 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVRENAME_H #define LVRENAME_H #include "kvpmdialog.h" class QLineEdit; class QRegExpValidator; class QString; class LogVol; class LVRenameDialog : public KvpmDialog { Q_OBJECT LogVol *m_lv; QString m_old_name; QString m_vg_name; QLineEdit *m_new_name; QRegExpValidator *m_name_validator; QString getNewMapperPath(); public: explicit LVRenameDialog(LogVol *const volume, QWidget *parent = nullptr); private slots: void validateName(QString name); void commit(); }; #endif kvpm-0.9.10/kvpm/vgrename.cpp0000644000175000017500000000607712770324126016305 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgrename.h" #include #include #include #include #include #include #include #include "logvol.h" #include "mounttables.h" #include "processprogress.h" #include "volgroup.h" VGRenameDialog::VGRenameDialog(VolGroup *const group, QWidget *parent) : KvpmDialog(parent), m_vg(group), m_old_name(group->getName()) { setCaption(i18n("Rename Volume Group")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); QLabel *label = new QLabel(i18n("Rename Volume Group")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); layout->addSpacing(10); label = new QLabel(i18n("Current volume group name: %1", m_old_name)); layout->addWidget(label); QLabel *const name_label = new QLabel(i18n("New volume group name: ")); m_new_name = new QLineEdit(); QRegExp rx("[0-9a-zA-Z_\\.][-0-9a-zA-Z_\\.]*"); m_name_validator = new QRegExpValidator(rx, m_new_name); m_new_name->setValidator(m_name_validator); QHBoxLayout *const name_layout = new QHBoxLayout(); name_layout->addWidget(name_label); name_layout->addWidget(m_new_name); layout->addLayout(name_layout); enableButtonOk(false); connect(m_new_name, SIGNAL(textChanged(QString)), this, SLOT(validateName(QString))); } void VGRenameDialog::commit() { hide(); QStringList args = QStringList() << "vgrename" << m_old_name << m_new_name->text(); const QString old_name = '/' + m_old_name + '/'; const QString new_name = '/' + m_new_name->text().trimmed() + '/'; ProcessProgress rename(args); if (!rename.exitCode()) { for (auto lv : m_vg->getLogicalVolumes()) { if (lv->isMounted()) { const QString old_path = lv->getMapperPath(); const QString new_path = lv->getMapperPath().replace(old_path.lastIndexOf(old_name), old_name.size(), new_name); MountTables::renameEntries(old_path, new_path); } } } } /* The allowed characters in the name are letters, numbers, periods hyphens and underscores. Also the names ".", ".." and names starting with a hyphen are disallowed */ void VGRenameDialog::validateName(QString name) { int pos = 0; if (m_name_validator->validate(name, pos) == QValidator::Acceptable && name != "." && name != "..") { enableButtonOk(true); } else { enableButtonOk(false); } } kvpm-0.9.10/kvpm/partremove.cpp0000644000175000017500000000342612770324126016660 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "partremove.h" #include "storagepartition.h" #include #include #include bool remove_partition(StoragePartition *const partition) { PedPartition *const ped_partition = partition->getPedPartition(); if (partition->getType() != "extended") { const QString warning = i18n("Delete partition: %1? Any data on that partition will be lost.", partition->getName()); if (KMessageBox::warningYesNo(nullptr, warning, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { if (ped_disk_delete_partition(ped_partition->disk, ped_partition)) ped_disk_commit(ped_partition->disk); return true; } } else { const QString question = i18n("Delete partition: %1?", partition->getName()); if (KMessageBox::questionYesNo(nullptr, question) == KMessageBox::Yes) { if (ped_disk_delete_partition(ped_partition->disk, ped_partition)) ped_disk_commit(ped_partition->disk); return true; } } return false; } kvpm-0.9.10/kvpm/vgextend.cpp0000644000175000017500000002014212770324126016312 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "vgextend.h" #include "masterlist.h" #include "misc.h" #include "progressbox.h" #include "pvgroupbox.h" #include "storagebase.h" #include "storagedevice.h" #include "storagepartition.h" #include "topwindow.h" #include "vgcreate.h" #include "volgroup.h" #include #include #include #include #include #include #include #include #include VGExtendDialog::VGExtendDialog(const VolGroup *const group, QWidget *parent) : KvpmDialog(parent), m_vg(group) { const QList devices(getUsablePvs()); if (devices.size() > 0) { if (continueWarning()) buildDialog(devices); else preventExec(); } else { preventExec(); KMessageBox::sorry(nullptr, i18n("No unused potential physical volumes found")); } } VGExtendDialog::VGExtendDialog(const VolGroup *const group, const StorageBase *const device, QWidget *parent) : KvpmDialog(parent), m_vg(group) { QList devices; devices << device; if (continueWarning()) buildDialog(devices); else preventExec(); } bool VGExtendDialog::continueWarning() { const QString warning = i18n("If a device or partition is added to a volume group, " "any data currently on that device or partition will be lost."); return (KMessageBox::warningContinueCancel(nullptr, warning, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) == KMessageBox::Continue); } void VGExtendDialog::commit() { const QByteArray vg_name = m_vg->getName().toLocal8Bit(); const QStringList pv_names = m_pv_checkbox->getNames(); lvm_t lvm = MasterList::getLvm(); vg_t vg_dm; ProgressBox *const progress_box = TopWindow::getProgressBox(); progress_box->setRange(0, pv_names.size()); progress_box->setText("Extending VG"); hide(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if ((vg_dm = lvm_vg_open(lvm, vg_name.data(), "w", 0))) { for (int i = 0; i < pv_names.size(); ++i) { progress_box->setValue(i); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); QByteArray name = pv_names[i].toLocal8Bit(); pv_create_params_t params = lvm_pv_params_create(lvm, name.data()); lvm_property_value_t value; value.is_settable = 1; value.is_string = 0; value.is_integer = 1; value.is_valid = 1; value.is_signed = 0; value.value.integer = m_copies_combo->currentIndex(); lvm_pv_params_set_property(params, "pvmetadatacopies", &value); if (m_size_edit->hasAcceptableInput()) { value.value.integer = 2 * m_size_edit->text().toInt(); lvm_pv_params_set_property(params, "pvmetadatasize", &value); } if (m_align_edit->hasAcceptableInput()) { value.value.integer = 2 * m_align_edit->text().toInt(); lvm_pv_params_set_property(params, "data_alignment", &value); } if (m_offset_edit->hasAcceptableInput()) { value.value.integer = 2 * m_offset_edit->text().toInt(); lvm_pv_params_set_property(params, "data_alignment_offset", &value); } lvm_pv_create_adv(params); if (lvm_vg_extend(vg_dm, name.data())) KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); } if (lvm_vg_write(vg_dm)) KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); lvm_vg_close(vg_dm); progress_box->reset(); return; } KMessageBox::error(nullptr, QString(lvm_errmsg(lvm))); progress_box->reset(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); return; } void VGExtendDialog::validateOK() { if (m_pv_checkbox->getRemainingSpace()) enableButtonOk(true); else enableButtonOk(false); } void VGExtendDialog::buildDialog(const QList devices) { setCaption(i18n("Extend Volume Group")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); QLabel *const title = new QLabel(i18n("Extend volume group: %1", m_vg->getName())); title->setAlignment(Qt::AlignCenter); layout->addSpacing(5); layout->addWidget(title); layout->addSpacing(10); QTabWidget *const tab_widget = new QTabWidget(this); layout->addWidget(tab_widget); tab_widget->addTab(buildGeneralTab(devices), "General"); tab_widget->addTab(buildAdvancedTab(), "Advanced"); } QWidget *VGExtendDialog::buildGeneralTab(const QList devices) { QWidget *tab = new(QWidget); QVBoxLayout *const layout = new QVBoxLayout(); tab->setLayout(layout); m_pv_checkbox = new PvGroupBox(devices, m_vg->getExtentSize()); layout->addWidget(m_pv_checkbox); connect(m_pv_checkbox, SIGNAL(stateChanged()), this, SLOT(validateOK())); return tab; } QWidget *VGExtendDialog::buildAdvancedTab() { QWidget *tab = new QWidget; QVBoxLayout *const layout = new QVBoxLayout(); tab->setLayout(layout); QHBoxLayout *const copies_layout = new QHBoxLayout(); QLabel *const copies_label = new QLabel("Metadata copies: "); copies_layout->addWidget(copies_label); m_copies_combo = new QComboBox(); m_copies_combo->addItem(i18n("0")); m_copies_combo->addItem(i18n("1")); m_copies_combo->addItem(i18n("2")); m_copies_combo->setCurrentIndex(1); copies_layout->addWidget(m_copies_combo); copies_layout->addStretch(); QLabel *const unit_label = new QLabel(i18n("All values are in KiloBytes")); unit_label->setAlignment(Qt::AlignCenter); QHBoxLayout *const size_layout = new QHBoxLayout(); QLabel *const size_label = new QLabel(i18n("Metadata size:")); size_layout->addWidget(size_label); m_size_edit = new QLineEdit; QIntValidator *const size_validator = new QIntValidator(); size_validator->setBottom(0); m_size_edit->setValidator(size_validator); m_size_edit->setPlaceholderText(i18n("default")); size_layout->addWidget(m_size_edit); QHBoxLayout *const align_layout = new QHBoxLayout(); QLabel *align_label = new QLabel(i18n("Metadata align:")); align_layout->addWidget(align_label); m_align_edit = new QLineEdit; QIntValidator *const align_validator = new QIntValidator(); align_validator->setBottom(0); m_align_edit->setValidator(align_validator); m_align_edit->setPlaceholderText(i18n("default")); align_layout->addWidget(m_align_edit); QHBoxLayout *const offset_layout = new QHBoxLayout(); QLabel *offset_label = new QLabel(i18n("Metadata offset:")); offset_layout->addWidget(offset_label); m_offset_edit = new QLineEdit; QIntValidator *const offset_validator = new QIntValidator(); offset_validator->setBottom(0); m_offset_edit->setValidator(offset_validator); m_offset_edit->setPlaceholderText(i18n("default")); offset_layout->addWidget(m_offset_edit); layout->addLayout(copies_layout); layout->addWidget(unit_label); layout->addLayout(size_layout); layout->addLayout(align_layout); layout->addLayout(offset_layout); return tab; } kvpm-0.9.10/kvpm/lvsizechart.cpp0000644000175000017500000001272412770324126017033 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvsizechart.h" #include #include #include #include #include #include "logvol.h" #include "lvsizechartseg.h" #include "volgroup.h" LVSizeChart::LVSizeChart(VolGroup *const group, QTreeWidget *const vgTree, QWidget *parent) : QFrame(parent), m_vg(group), m_vg_tree(vgTree) { m_layout = new QHBoxLayout(this); m_layout->setSpacing(0); m_layout->setMargin(0); m_layout->setSizeConstraint(QLayout::SetNoConstraint); populateChart(); setLayout(m_layout); setMinimumHeight(45); setMaximumHeight(75); connect(m_vg_tree->header(), SIGNAL(sectionClicked(int)), this, SLOT(vgtreeClicked())); } void LVSizeChart::populateChart() { QLayoutItem *child; while ((child = m_layout->takeAt(0))) // remove old children of layout delete child; LvList volumes; const long item_count = m_vg_tree->topLevelItemCount(); for (int x = 0; x < item_count; ++x) { QTreeWidgetItem *const item = m_vg_tree->topLevelItem(x); LogVol *const lv = m_vg->getLvByName(item->data(0, Qt::UserRole).toString()); if (lv) { volumes.append(lv); if (lv->isThinPool()){ for (auto thin : lv->getThinVolumes()) { for (auto snap : thin->getSnapshots()) { if (snap->isCowSnap()) volumes.append(snap); } } } else if (lv->getSnapshotCount()) { volumes.append(lv->getSnapshots()); } } } const long long free_extents = m_vg->getFreeExtents(); const long long total_extents = m_vg->getExtents(); const long long extent_size = m_vg->getExtentSize(); QString usage; // Filesystem: blank, ext2 etc. or freespace in vg int max_segment_width; double seg_ratio; QWidget *widget; for (auto lv : volumes) { if (!lv->isLvmMirrorLeg() && !lv->isThinVolume() && !lv->isLvmMirrorLog() && !lv->isThinPoolData() && !lv->isVirtual() && !lv->isRaidImage() && !(lv->isLvmMirror() && !(lv->getOrigin()).isEmpty())) { if (lv->isThinPool()) usage = "thin_pool"; else usage = lv->getFilesystem(); if (lv->isLvmMirror() || lv->isRaid() || lv->isThinPool()) seg_ratio = (lv->getTotalSize() / (double) extent_size) / (double) total_extents; else seg_ratio = lv->getExtents() / (double) total_extents; m_ratios.append(seg_ratio); widget = frameAndConnect(new LVChartSeg(lv, usage, this)); m_layout->addWidget(widget); m_widgets.append(widget); } } if (free_extents) { // only create a free space widget if we have some seg_ratio = (free_extents / (double) total_extents) + 0.02; // allow a little "stretch" 0.02 usage = "freespace" ; widget = frameAndConnect(new LVChartSeg(nullptr, usage, this)); m_widgets.append(widget); m_layout->addWidget(widget); m_ratios.append(seg_ratio); } else if (m_widgets.size() == 0) { // if we have no chart segs then put in a blank one // because lvsizechart won't work with zero segments usage = "" ; widget = frameAndConnect(new LVChartSeg(nullptr, usage, this)); m_widgets.append(widget); m_layout->addWidget(widget); m_ratios.append(1.0); } max_segment_width = (int)(width() * m_ratios[0]); if (max_segment_width < 1) max_segment_width = 1; m_widgets[0]->setMaximumWidth(max_segment_width); for (int x = m_widgets.size() - 1; x >= 1 ; --x) { max_segment_width = qRound(((double)width() * m_ratios[x])); if (max_segment_width < 1) max_segment_width = 1; m_widgets[x]->setMaximumWidth(max_segment_width); } } void LVSizeChart::resizeEvent(QResizeEvent *event) { const int new_width = (event->size()).width(); int max_segment_width = (int)(new_width * m_ratios[0]); if (max_segment_width < 1) max_segment_width = 1; m_widgets[0]->setMaximumWidth(max_segment_width); for (int x = m_widgets.size() - 1; x >= 1 ; --x) { max_segment_width = qRound(((double)width() * m_ratios[x])); if (max_segment_width < 1) max_segment_width = 1; m_widgets[x]->setMaximumWidth(max_segment_width); } } void LVSizeChart::vgtreeClicked() { populateChart(); } QFrame* LVSizeChart::frameAndConnect(LVChartSeg *const seg) { connect(seg, SIGNAL(lvMenuRequested(LogVol *)), this, SIGNAL(lvMenuRequested(LogVol *))); QFrame *const frame = new QFrame(); frame->setFrameStyle(QFrame::Sunken | QFrame::Panel); frame->setLineWidth(2); QVBoxLayout *const layout = new QVBoxLayout(); layout->addWidget(seg); layout->setSpacing(0); layout->setMargin(0); frame->setLayout(layout); return frame; } kvpm-0.9.10/kvpm/lvactionsmenu.cpp0000644000175000017500000000424012770324126017356 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvactionsmenu.h" #include "logvol.h" #include "lvactions.h" #include "volgroup.h" #include #include LVActionsMenu::LVActionsMenu(LVActions *const lvactions, QWidget *parent) : QMenu(parent) { addAction(lvactions->action("lvcreate")); addAction(lvactions->action("thinpool")); addAction(lvactions->action("thincreate")); addSeparator(); addAction(lvactions->action("lvremove")); addSeparator(); addAction(lvactions->action("lvrename")); addAction(lvactions->action("snapcreate")); addAction(lvactions->action("thinsnap")); addAction(lvactions->action("snapmerge")); addAction(lvactions->action("lvreduce")); addAction(lvactions->action("lvextend")); addAction(lvactions->action("pvmove")); addAction(lvactions->action("pvmove")); addAction(lvactions->action("lvchange")); addSeparator(); QMenu *const raid_menu = new QMenu(i18n("Mirrors and RAID"), this); raid_menu->addAction(lvactions->action("addlegs")); raid_menu->addAction(lvactions->action("changelog")); raid_menu->addAction(lvactions->action("removemirror")); raid_menu->addAction(lvactions->action("removethis")); raid_menu->addSeparator(); raid_menu->addAction(lvactions->action("repairmissing")); raid_menu->addAction(lvactions->action("resync")); addMenu(raid_menu); QMenu *const fs_menu = new QMenu(i18n("Filesystem operations"), this); fs_menu->addAction(lvactions->action("mount")); fs_menu->addAction(lvactions->action("unmount")); fs_menu->addSeparator(); fs_menu->addAction(lvactions->action("maxfs")); fs_menu->addAction(lvactions->action("fsck")); fs_menu->addSeparator(); fs_menu->addAction(lvactions->action("mkfs")); addMenu(fs_menu); } kvpm-0.9.10/kvpm/fsreduce.h0000644000175000017500000000117512770324126015740 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2011, 2012 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef FSREDUCE_H #define FSREDUCE_H class QString; bool fs_can_reduce(const QString fs); long long fs_reduce(const QString path, const long long new_size, const QString fs); long long get_min_fs_size(const QString path, const QString fs); #endif kvpm-0.9.10/kvpm/vgactions.h0000644000175000017500000000131312770324126016127 0ustar benscottbenscott/* * * * Copyright (C) 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGACTIONS_H #define VGACTIONS_H #include class QAction; class VolGroup; class VGActions : public KActionCollection { Q_OBJECT VolGroup *m_vg = nullptr; public: VGActions(QWidget *parent = nullptr); void setVg(VolGroup *vg); private slots: void callDialog(QAction *); }; #endif kvpm-0.9.10/kvpm/lvreduce.cpp0000644000175000017500000001470612770324126016310 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "lvreduce.h" #include #include #include #include #include #include #include "fsreduce.h" #include "logvol.h" #include "processprogress.h" #include "misc.h" #include "sizeselectorbox.h" #include "volgroup.h" LVReduceDialog::LVReduceDialog(LogVol *const volume, QWidget *parent) : KvpmDialog(parent), m_lv(volume) { if (volume->isThinPool()) setCaption(i18n("Reduce Thin Pool")); else setCaption(i18n("Reduce Logical Volume")); QWidget *const dialog_body = new QWidget(this); setMainWidget(dialog_body); QVBoxLayout *const layout = new QVBoxLayout(); dialog_body->setLayout(layout); const long long extent_size = m_lv->getVg()->getExtentSize(); const long long current_extents = m_lv->getExtents(); const QString fs = m_lv->getFilesystem(); long long min_extents; bool force = false; const QString warning_message1 = i18n("If this Inactive logical volume is reduced " "any data it contains will be lost!"); const QString warning_message2 = i18n("Only the ext2, ext3 and ext4 file systems " "are supported for file system reduction. If this " "logical volume is reduced any data it contains " "will be lost!"); if (!m_lv->isActive() && !m_lv->isCowSnap()) { if (KMessageBox::warningContinueCancel(nullptr, warning_message1, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) == KMessageBox::Continue) { force = true; } else { preventExec(); } } else if (m_lv->isMounted() && !m_lv->isCowSnap()) { KMessageBox::error(nullptr, i18n("The filesystem must be unmounted first")); preventExec(); } else if (m_lv->isThinPool()) { KMessageBox::error(nullptr, i18n("Reducing thin pools isn't supported yet")); preventExec(); } else if (!fs_can_reduce(fs) && !m_lv->isCowSnap()) { if (KMessageBox::warningContinueCancel(nullptr, warning_message2, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) == KMessageBox::Continue) { force = true; } else { preventExec(); } } if (willExec() && !force && !m_lv->isCowSnap()) { const long long min_fs = get_min_fs_size(m_lv->getMapperPath(), m_lv->getFilesystem()); if (min_fs % extent_size) min_extents = 1 + (min_fs / extent_size); else min_extents = min_fs / extent_size; if (min_fs == 0 || min_extents >= m_lv->getExtents()) { KMessageBox::error(nullptr, i18n("The filesystem is already as small as it can be")); preventExec(); } } else { min_extents = 1; } if (willExec()) { bool use_si_units; KConfigSkeleton skeleton; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", use_si_units, false); KFormat::BinaryUnitDialect dialect; if (use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; QVBoxLayout *const label_layout = new QVBoxLayout(); QWidget *const label_widget = new QWidget(); label_widget->setLayout(label_layout); QLabel *const lv_name_label = new QLabel(); if (m_lv->isThinPool()) lv_name_label->setText(i18n("Reduce thin pool: %1", m_lv->getName())); else lv_name_label->setText(i18n("Reduce logical volume: %1", m_lv->getName())); lv_name_label->setAlignment(Qt::AlignCenter); QLabel *const lv_min_label = new QLabel(i18n("Estimated minimum size: %1", KFormat().formatByteSize(min_extents * extent_size, 1, dialect))); m_size_selector = new SizeSelectorBox(extent_size, min_extents, current_extents, current_extents, true, false, true); label_layout->addWidget(lv_name_label); label_layout->addSpacing(10); label_layout->addWidget(lv_min_label); label_layout->addSpacing(5); layout->addWidget(label_widget); layout->addWidget(m_size_selector); connect(m_size_selector, SIGNAL(stateChanged()), this , SLOT(resetOkButton())); resetOkButton(); } } void LVReduceDialog::commit() { const QString fs = m_lv->getFilesystem(); const long long target_size = m_size_selector->getNewSize() * m_lv->getVg()->getExtentSize(); long long new_size; hide(); if (m_lv->isCowSnap() || !m_lv->isActive()) // never reduce the fs of a snap! new_size = target_size; else if (fs_can_reduce(fs)) new_size = fs_reduce(m_lv->getMapperPath(), target_size, fs); else new_size = target_size; if (new_size) { QStringList args = QStringList() << "lvreduce" << "--force" << "--size" << QString("%1K").arg(new_size / 1024) << m_lv->getMapperPath(); ProcessProgress reduce_lv(args); } } void LVReduceDialog::resetOkButton() { const long long diff = m_size_selector->getMaximumSize() - m_size_selector->getNewSize(); enableButtonOk(m_size_selector->isValid() && diff); } kvpm-0.9.10/kvpm/lvsizechartseg.h0000644000175000017500000000151212770324126017170 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVSIZECHARTSEG_H #define LVSIZECHARTSEG_H #include class QBrush; class QString; class LogVol; class LVChartSeg : public QWidget { Q_OBJECT LogVol *m_lv = nullptr; QBrush m_brush; public: LVChartSeg(LogVol *const volume, const QString use, QWidget *parent); void paintEvent(QPaintEvent *); private slots: void popupContextMenu(QPoint); signals: void lvMenuRequested(LogVol *lv); }; #endif kvpm-0.9.10/kvpm/pvproperties.h0000644000175000017500000000134412770324126016700 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVPROPERTIES_H #define PVPROPERTIES_H #include class LVSegmentExtent; class PhysVol; class PVProperties : public QWidget { PhysVol *m_pv; QFrame *buildMdaBox(); QFrame *buildLvBox(); QFrame *buildUuidBox(); public: explicit PVProperties(PhysVol *const volume, QWidget *parent = nullptr); }; #endif kvpm-0.9.10/kvpm/sizeselectorbox.cpp0000644000175000017500000003606512770324126017725 0ustar benscottbenscott/* * * * Copyright (C) 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "sizeselectorbox.h" #include #include #include #include #include #include #include SizeSelectorBox::SizeSelectorBox(long long unitSize, long long minSize, long long maxSize, long long initialSize, bool isVolume, bool isOffset, bool isNew, bool startLocked, QWidget *parent) : QGroupBox(parent), m_max_size(maxSize), m_min_size(minSize), m_unit_size(unitSize), m_is_volume(isVolume), m_is_offset(isOffset), m_is_new(isNew), m_start_locked(startLocked) { if (m_min_size < 0) m_min_size = 0; if (m_max_size < 0) m_max_size = 0; if (m_min_size > m_max_size) m_min_size = m_max_size; m_initial_size = initialSize; m_current_size = initialSize; m_is_valid = true; m_constrained_max = m_max_size; m_constrained_min = m_min_size; KConfigSkeleton skeleton; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", m_use_si_units, false); QVBoxLayout *const layout = new QVBoxLayout(); QHBoxLayout *const upper_layout = new QHBoxLayout(); m_size_slider = new QSlider(Qt::Horizontal); m_size_slider->setRange(0, 100); m_size_edit = new QLineEdit(); m_size_edit->setAlignment(Qt::AlignRight); m_suffix_combo = new QComboBox(); if (m_use_si_units) { m_suffix_combo->insertItem(0, "MB"); m_suffix_combo->insertItem(1, "GB"); m_suffix_combo->insertItem(2, "TB"); } else { m_suffix_combo->insertItem(0, "MiB"); m_suffix_combo->insertItem(1, "GiB"); m_suffix_combo->insertItem(2, "TiB"); } if (m_is_volume) { m_suffix_combo->insertItem(0, i18n("Extents")); m_suffix_combo->setCurrentIndex(2); } else { m_suffix_combo->setCurrentIndex(1); } m_suffix_combo->setInsertPolicy(QComboBox::NoInsert); if (m_is_new) { if (!m_is_offset) m_current_size = m_constrained_max; else m_current_size = 0; } m_size_validator = new QDoubleValidator(m_size_edit); m_size_edit->setValidator(m_size_validator); m_size_validator->setBottom(0); m_size_box = NULL; if (m_is_volume) { setTitle(i18n("Volume Size")); if (!m_is_new) { m_size_box = new QCheckBox(i18n("Lock selected size")); layout->addWidget(m_size_box); m_size_box->setChecked(false); m_size_box->setEnabled(false); m_size_box->hide(); setConstraints(false); connect(m_size_box, SIGNAL(toggled(bool)), this, SLOT(lock(bool))); } } else if (m_is_offset) { setTitle(i18n("Partition Start")); m_offset_box = new QCheckBox(i18n("Lock partition start")); setConstraints(false); layout->addWidget(m_offset_box); connect(m_offset_box, SIGNAL(toggled(bool)), this, SLOT(lock(bool))); } else { setTitle(i18n("Partition Size")); m_size_box = new QCheckBox(i18n("Lock selected size")); layout->addWidget(m_size_box); connect(m_size_box, SIGNAL(toggled(bool)), this, SLOT(lock(bool))); if (!m_is_new) { m_shrink_box = new QCheckBox(i18n("Prevent shrinking")); m_shrink_box->setChecked(false); setConstraints(false); layout->addWidget(m_shrink_box); connect(m_shrink_box, SIGNAL(toggled(bool)), this, SLOT(lockShrink(bool))); connect(m_size_box, SIGNAL(toggled(bool)), this, SLOT(disableLockShrink(bool))); } } QLabel *const edit_label = new QLabel(); if (m_is_offset) edit_label->setText(i18n("New start:")); else edit_label->setText(i18n("New size:")); edit_label->setBuddy(m_size_edit); upper_layout->addWidget(edit_label); upper_layout->addWidget(m_size_edit); upper_layout->addWidget(m_suffix_combo); layout->addLayout(upper_layout); layout->addWidget(m_size_slider); setLayout(layout); updateSlider(); updateEdit(); updateValidator(); connect(m_size_edit, SIGNAL(textEdited(QString)), this, SLOT(setToEdit(QString))); connect(m_size_slider, SIGNAL(sliderMoved(int)), this, SLOT(setToSlider(int))); connect(m_suffix_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateEdit())); if (m_min_size == m_max_size) { setNewSize(m_min_size); if (m_is_offset) { m_offset_box->setChecked(true); m_offset_box->setEnabled(false); } else { m_size_box->setChecked(true); m_size_box->setEnabled(false); } } } void SizeSelectorBox::resetToInitial() { if (m_size_box != NULL) { if (!m_size_box->isEnabled()) return; } m_current_size = m_initial_size; if (m_is_volume) { if (!m_is_new) { m_shrink_box->setChecked(false); } } else if (m_is_offset) { lock(m_start_locked); m_offset_box->setChecked(m_start_locked); } else { lock(m_start_locked); m_size_box->setChecked(m_start_locked); if (!m_is_new) { m_shrink_box->setChecked(false); } } updateEdit(); updateSlider(); } void SizeSelectorBox::setToSlider(int value) { long long new_size = ((m_constrained_max - m_constrained_min) * ((double)value / 100)) + m_constrained_min; m_size_slider->setValue(value); if ((value >= 100) || (new_size > m_constrained_max)) m_current_size = m_constrained_max; else if ((value <= 0) || (new_size < m_constrained_min)) m_current_size = m_constrained_min; else m_current_size = new_size; updateEdit(); } bool SizeSelectorBox::setNewSize(long long size) { if (isLocked()) { return false; } else if (size > m_constrained_max) { return false; } else if (size < m_constrained_min) { return false; } else { m_current_size = size; updateEdit(); updateSlider(); return true; } } void SizeSelectorBox::updateSlider() { int percent = qRound(100.0 * ((double)(m_current_size - m_constrained_min) / (m_constrained_max - m_constrained_min))); if (percent > 100) percent = 100; else if (percent < 0) percent = 0; m_size_slider->setValue(percent); } void SizeSelectorBox::setConstrainedMax(long long max) { if (isLocked() || !m_size_edit->isEnabled() || max == m_constrained_max) return; if ((max < 0) || (max > m_max_size)) max = m_max_size; m_constrained_max = max; if (m_constrained_min > m_constrained_max) m_constrained_min = m_constrained_max; if (!m_is_volume) { if (m_current_size > m_constrained_max) { setNewSize(m_constrained_max); } } updateEdit(); updateSlider(); } long long SizeSelectorBox::getNewSize() { return m_current_size; } long long SizeSelectorBox::getMaximumSize() { return m_constrained_max; } long long SizeSelectorBox::getMinimumSize() { return m_constrained_min; } void SizeSelectorBox::lock(bool lock) { setConstraints(lock); emit stateChanged(); } void SizeSelectorBox::disableLockShrink(bool disable) { m_shrink_box->setEnabled(!disable); } void SizeSelectorBox::setConstraints(bool lock) { if (lock) { if (!m_is_valid) { if (m_current_size > m_constrained_max) m_current_size = m_constrained_max; if (m_current_size < m_constrained_min) m_current_size = m_constrained_min; updateEdit(); } if (m_is_offset) { m_constrained_min = m_min_size; m_constrained_max = m_max_size; } else { m_constrained_min = m_current_size; m_constrained_max = m_current_size; } m_size_edit->setEnabled(false); m_size_slider->setEnabled(false); m_suffix_combo->setEnabled(false); } else { if (!m_is_valid) { if (m_current_size > m_constrained_max) m_current_size = m_constrained_max; if (m_current_size < m_constrained_min) m_current_size = m_constrained_min; updateEdit(); } m_constrained_min = m_min_size; m_size_edit->setEnabled(true); m_size_slider->setEnabled(true); m_suffix_combo->setEnabled(true); m_constrained_max = m_max_size; updateValidator(); updateSlider(); } } void SizeSelectorBox::lockShrink(bool lock) { if (lock) { if (!m_is_valid) { if (m_current_size > m_constrained_max) m_current_size = m_constrained_max; if (m_current_size < m_constrained_min) m_current_size = m_constrained_min; updateEdit(); } m_constrained_min = m_initial_size; if (m_current_size < m_constrained_min) { m_current_size = m_constrained_min; updateValidator(); updateSlider(); } } else { if (!m_is_valid) { if (m_current_size > m_constrained_max) m_current_size = m_constrained_max; if (m_current_size < m_constrained_min) m_current_size = m_constrained_min; updateEdit(); } m_constrained_min = m_min_size; updateValidator(); updateSlider(); } emit stateChanged(); } void SizeSelectorBox::setToEdit(QString size) { long long proposed_size; int x = 0; if (m_size_validator->validate(size, x) == QValidator::Acceptable) { if (m_suffix_combo->currentIndex() == 0 && m_is_volume) proposed_size = qRound64(size.toDouble()); else proposed_size = convertSizeToUnits(m_suffix_combo->currentIndex(), size.toDouble()); if ((proposed_size >= m_constrained_min) && (proposed_size <= m_constrained_max)) { m_current_size = proposed_size; m_is_valid = true; emit stateChanged(); return; } } if (m_is_volume) { // if the size it out of range we try to set it anyway if (size.isEmpty()) { m_current_size = -1; } else { if (m_suffix_combo->currentIndex() == 0) proposed_size = size.toLongLong(); else proposed_size = convertSizeToUnits(m_suffix_combo->currentIndex(), size.toDouble()); if (proposed_size < 0) proposed_size = 0; m_current_size = proposed_size; } } m_is_valid = false; emit stateChanged(); } long long SizeSelectorBox::convertSizeToUnits(int index, double size) { if (m_is_volume) index -= 1; long double partition_size = size; if (m_use_si_units) { if (index == 0) partition_size *= (long double)1.0E6; else if (index == 1) partition_size *= (long double)1.0E9; else { partition_size *= (long double)1.0E6; partition_size *= (long double)1.0E6; } } else { if (index == 0) partition_size *= (long double)0x100000; else if (index == 1) partition_size *= (long double)0x40000000; else { partition_size *= (long double)0x100000; partition_size *= (long double)0x100000; } } partition_size /= m_unit_size; if (partition_size < 0) partition_size = 0; return qRound64((double)partition_size); } void SizeSelectorBox::updateEdit() { long double sized = (long double)m_current_size * m_unit_size; int index = m_suffix_combo->currentIndex(); updateValidator(); if (m_is_volume) index -= 1; if (m_use_si_units) { if (index == 0) sized /= (long double)1.0E6; else if (index == 1) sized /= (long double)1.0E9; else { sized /= (long double)1.0E6; sized /= (long double)1.0E6; } } else { if (index == 0) sized /= (long double)0x100000; else if (index == 1) sized /= (long double)0x40000000; else { sized /= (long double)0x100000; sized /= (long double)0x100000; } } if (index == -1) m_size_edit->setText(QString("%1").arg((double)m_current_size, 0, 'g', 5)); else m_size_edit->setText(QString("%1").arg((double)sized, 0, 'g', 5)); if ((m_current_size >= m_constrained_min) && (m_current_size <= m_constrained_max)) m_is_valid = true; else m_is_valid = false; emit stateChanged(); } void SizeSelectorBox::updateValidator() { long double valid_topd = (long double)m_constrained_max * m_unit_size; long double valid_bottomd = (long double)m_constrained_min * m_unit_size; int index = m_suffix_combo->currentIndex(); if (m_is_volume) index -= 1; if (m_use_si_units) { if (index == 0) { valid_topd /= (long double)1.0E6; valid_bottomd /= (long double)1.0E6; } else if (index == 1) { valid_topd /= (long double)1.0E9; valid_bottomd /= (long double)1.0E9; } else { valid_topd /= (long double)1.0E6; valid_topd /= (long double)1.0E6; valid_bottomd /= (long double)1.0E6; valid_bottomd /= (long double)1.0E6; } } else { if (index == 0) { valid_topd /= (long double)0x100000; valid_bottomd /= (long double)0x100000; } else if (index == 1) { valid_topd /= (long double)0x40000000; valid_bottomd /= (long double)0x40000000; } else { valid_topd /= (long double)0x100000; valid_topd /= (long double)0x100000; valid_bottomd /= (long double)0x100000; valid_bottomd /= (long double)0x100000; } } if (valid_bottomd < 0) valid_bottomd = 0; if (index != -1) { m_size_validator->setTop((double)valid_topd); m_size_validator->setBottom((double)valid_bottomd); } else { m_size_validator->setTop((double)m_constrained_max); m_size_validator->setBottom((double)m_constrained_min); } } bool SizeSelectorBox::isValid() { return m_is_valid; } bool SizeSelectorBox::isLocked() { if (m_is_offset) { return m_offset_box->isChecked(); } else return m_size_box->isChecked(); } bool SizeSelectorBox::usingBytes() { if (m_is_volume && (m_suffix_combo->currentIndex() == 0)) return false; else return true; } kvpm-0.9.10/kvpm/pvchange.h0000644000175000017500000000206312770324126015730 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef PVCHANGE_H #define PVCHANGE_H #include #include "kvpmdialog.h" class QCheckBox; class QComboBox; class QGroupBox; class QLabel; class QLineEdit; class PhysVol; class PVChangeDialog : public KvpmDialog { Q_OBJECT QLineEdit *m_tag_edit; // new tag to add QComboBox *m_deltag_combo; // old tag to remove QGroupBox *m_tags_group; QCheckBox *m_allocation_box; QCheckBox *m_uuid_box; QCheckBox *m_mda_box; PhysVol *m_pv; QStringList arguments(); public: explicit PVChangeDialog(PhysVol *physicalVolume, QWidget *parent = nullptr); private slots: void commit(); void resetOkButton(); }; #endif kvpm-0.9.10/kvpm/partchange.cpp0000644000175000017500000005322512770324126016612 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "partchange.h" #include "fsextend.h" #include "fsreduce.h" #include "pedexceptions.h" #include "physvol.h" #include "processprogress.h" #include "progressbox.h" #include "pvextend.h" #include "pvreduce.h" #include "storagepartition.h" #include "topwindow.h" #include #include #include #include #include PartitionChangeDialog::PartitionChangeDialog(StoragePartition *const partition, QWidget *parent) : PartitionDialogBase(partition, parent), m_old_storage_part(partition) { m_bailout = true; if (partition->isPhysicalVolume()) { KMessageBox::information(nullptr, i18n("Resizing physical volumes has been so buggy that I have disabled that functionality")); return; } if (!hasInitialErrors()) { if (continueBackup()) { if (continueResize()) { if (continueBusy()) { validateChange(); connect(this, SIGNAL(changed()), this, SLOT(validateChange())); connect(this, SIGNAL(okClicked()), this, SLOT(commitPartition())); m_bailout = false; } } } } } bool PartitionChangeDialog::continueBackup() { const QString warning2 = i18n("Changes to the partition table can cause unintentional and permanent data" " loss. If the partition holds important data, you really should back it" " up before continuing."); if (KMessageBox::warningContinueCancel(nullptr, warning2, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return false; } return true; } bool PartitionChangeDialog::continueResize() { const QString message = i18n("Shrinking this filesystem is not supported, only growing or" " moving it is possible." "\n\n" "Note: currently only ext2, ext3, ext4 and physical volumes can be shrunk."); const QString warning = i18n("If this partition is enlarged, any filesystem or data on it" " will need to be extended separately. Shrinking this" " partition is not supported." "\n\n" "Note: currently only ext2, ext3, ext4 and physical volumes" " can be both shrunk and grown," " while Reiserfs, ntfs, jfs and xfs can be grown only."); const QString fs = m_old_storage_part->getFilesystem(); if (!(fs_can_reduce(fs) || m_old_storage_part->isPhysicalVolume())) { if(fs_can_extend(fs, m_old_storage_part->isMounted())) { KMessageBox::information(nullptr, message); } else if (KMessageBox::warningContinueCancel(nullptr, warning, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return false; } } return true; } bool PartitionChangeDialog::continueBusy() { const QString warning = i18n("This partition is on the same device with partitions that are busy or mounted. " "If at all possible they should be unmounted before proceeding. Otherise " "changes to the partition table may not be recognized by the kernel."); if (ped_device_is_busy(getPedPartition()->disk->dev)) { if (KMessageBox::warningContinueCancel(nullptr, warning, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Continue) { return false; } } return true; } void PartitionChangeDialog::commitPartition() { const PedSector new_size = getNewSize(); const PedSector new_offset = getNewOffset(); bool grow = false; bool shrink = false; bool move = ((getMaxStart() + new_offset) != getCurrentStart()); if (new_size < getCurrentSize()) { grow = false; shrink = true; } else if (new_size > getCurrentSize()) { grow = true; shrink = false; } else { grow = false; shrink = false; } qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if (grow && move) { if (movePartition()) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); growPartition(); } } else if (grow) { growPartition(); } else if (shrink && move) { if (shrinkPartition()) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); movePartition(); } } else if (shrink) { shrinkPartition(); } else if (move) { movePartition(); } qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } void PartitionChangeDialog::validateChange() { const PedSector ONE_MIB = 0x100000 / getSectorSize(); // sectors per megabyte const PedSector max_start = getMaxStart(); const PedSector max_end = getMaxEnd(); const PedSector current_size = getCurrentSize(); const PedSector current_start = getCurrentStart(); const PedSector preceding_sectors = getNewOffset(); const PedSector new_start = max_start + preceding_sectors; const PedSector new_size = getNewSize(); const PedSector following_sectors = getMaxSize() - (preceding_sectors + new_size); if (!isValid() || (preceding_sectors < 0) || (following_sectors < 0)) { (button(KDialog::Ok))->setEnabled(false); return; } else { updateGraphicAndLabels(); bool moving = false; bool resize = false; // we don't move if the move is less than 1 megabyte // and check that we have at least 1 meg to spare if (fabs(current_start - new_start) >= ONE_MIB){ // moving more than 1 MiB if ((new_start < current_start) && ((current_start - max_start) >= ONE_MIB)) { // moving left moving = true; } else if ((max_end - (current_start + current_size - 1)) >= ONE_MIB){ // moving right moving = true; } } if (fabs(current_size - new_size) >= ONE_MIB){ // resizing by more than 1 MiB? resize = true; } if (resize || moving) button(KDialog::Ok)->setEnabled(true); else button(KDialog::Ok)->setEnabled(false); } } bool PartitionChangeDialog::movefs(PedSector from_start, PedSector to_start, PedSector length) { const long long blocksize = 8000; // sectors moved in each block PedDevice *const device = getPedPartition()->disk->dev; const long long blockcount = length / blocksize; const long long extra = length % blocksize; bool success = true; if (ped_device_open(device)) { void *const buff = malloc(blocksize * getSectorSize()); ProgressBox *const progress_box = TopWindow::getProgressBox(); progress_box->setRange(0, blockcount); progress_box->setText(i18n("Moving data")); qApp->setOverrideCursor(Qt::WaitCursor); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if (to_start < from_start) { // moving left for (long long x = 0; x < blockcount; x++) { if ( !(x % 5) ) qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if (!ped_device_read(device, buff, from_start + (x * blocksize), blocksize)) { qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, i18n("Move failed: could not read from device")); success = false; break; } else if (!ped_device_write(device, buff, to_start + (x * blocksize), blocksize)) { qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, i18n("Move failed: could not write to device")); success = false; break; } progress_box->setValue(x); } if (success) { if (!ped_device_read(device, buff, from_start + (blockcount * blocksize), extra)) { qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, i18n("Move failed: could not read from device")); success = false; } else if (!ped_device_write(device, buff, to_start + (blockcount * blocksize), extra)) { qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, i18n("Move failed: could not write to device")); success = false; } } } else { // moving right if (!ped_device_read(device, buff, from_start + (blockcount * blocksize), extra)) { qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, i18n("Move failed: could not read from device")); success = false; } else if (!ped_device_write(device, buff, to_start + (blockcount * blocksize), extra)) { qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, i18n("Move failed: could not write to device")); success = false; } if (success) { for (long long x = blockcount - 1; x >= 0 ; x--) { if ( !(x % 5) ) qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if (!ped_device_read(device, buff, from_start + (x * blocksize), blocksize)) { qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, i18n("Move failed: could not read from device")); success = false; break; } else if (!ped_device_write(device, buff, to_start + (x * blocksize), blocksize)) { qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, i18n("Move failed: could not write to device")); success = false; break; } progress_box->setValue(blockcount - x); } } } free(buff); progress_box->reset(); progress_box->setRange(0, 0); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); progress_box->setText(i18n("Syncing device")); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if (!ped_device_sync(device)) success = false; qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if (!ped_device_close(device)) success = false; progress_box->reset(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); qApp->restoreOverrideCursor(); } else { success = false; } return success; } bool PartitionChangeDialog::shrinkPartition() { hide(); const PedSector ONE_MIB = 0x100000 / getSectorSize(); // sectors per megabyte PedDisk *const disk = getPedPartition()->disk; PedDevice *const device = disk->dev; const PedSector current_start = getCurrentStart(); const PedSector current_size = getCurrentSize(); const QString fs = m_old_storage_part->getFilesystem(); const bool is_pv = m_old_storage_part->isPhysicalVolume(); PedSector new_size = getNewSize(); if (new_size >= current_size) return false; if (new_size < getMinSize()) new_size = getMinSize(); else if (new_size > getMaxSize()) new_size = 1 + getMaxSize(); PedSector reduced_size = getNewSize(); if (is_pv) reduced_size = pv_reduce(getPath(), new_size * getSectorSize()); else reduced_size = fs_reduce(getPath(), new_size * getSectorSize(), fs); new_size = reduced_size / getSectorSize(); if (reduced_size % getSectorSize()) new_size++; if (new_size == 0) { // The shrink failed KMessageBox::error(nullptr, i18n("Filesystem shrink failed")); return false; } // This constraint assures we have a new partition at least as long as the fs can shrink it // We allow up to an extra 1MiB sectors for the end of the partition PedAlignment *const start_alignment = ped_alignment_new(0, 1); PedGeometry *const start_range = ped_geometry_new(device, current_start, 1); PedGeometry *end_range; PedAlignment *end_alignment; PedSector maximum_size; PedSector minimum_size = new_size; if (current_start + new_size - 1 > getMaxEnd()) { end_alignment = ped_alignment_new(0, 1); end_range = ped_geometry_new(device, current_start + new_size - 1, 1); maximum_size = minimum_size; } else { end_alignment = ped_alignment_new(-1, ONE_MIB); end_range = ped_geometry_new(device, current_start + new_size - 1, ONE_MIB); maximum_size = new_size + ONE_MIB - 1; } qApp->setOverrideCursor(Qt::WaitCursor); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); PedConstraint *constraint = ped_constraint_new(start_alignment, end_alignment, start_range, end_range, minimum_size, maximum_size); int success = ped_disk_set_partition_geom(disk, getPedPartition(), constraint, current_start, current_start + maximum_size); qApp->restoreOverrideCursor(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); ped_constraint_destroy(constraint); ped_alignment_destroy(start_alignment); ped_alignment_destroy(end_alignment); ped_geometry_destroy(start_range); ped_geometry_destroy(end_range); if (!success) { KMessageBox::error(nullptr, i18n("Partition shrink failed")); return false; } else { pedCommitAndWait(disk); return true; } } bool PartitionChangeDialog::growPartition() { hide(); const PedSector ONE_MIB = 0x100000 / getSectorSize(); // sectors per megabyte PedDisk *const disk = getPedPartition()->disk; PedDevice *const device = disk->dev; const QString fs = m_old_storage_part->getFilesystem(); const bool is_pv = m_old_storage_part->isPhysicalVolume(); const PedSector current_start = getCurrentStart(); const PedSector current_size = getCurrentSize(); const PedSector max_end = getMaxEnd(); const PedSector max_start = getMaxStart(); int success; PedSector min_new_size, max_new_size; // max desired size PedSector proposed_new_size = getNewSize(); if ((proposed_new_size - 1 + current_start) > max_end) proposed_new_size = 1 + max_end - current_start; if (proposed_new_size <= current_size) return true; if (proposed_new_size < current_size + ONE_MIB) { if (current_start + ONE_MIB <= max_end) { max_new_size = current_size + ONE_MIB; min_new_size = current_size; } else { max_new_size = max_end - current_start + 1; min_new_size = current_size; } } else if (proposed_new_size + ONE_MIB >= 1 + max_end - current_start) { max_new_size = 1 + max_end - current_start; min_new_size = max_new_size - ONE_MIB; } else { max_new_size = proposed_new_size + ONE_MIB - 1; min_new_size = proposed_new_size; } qApp->processEvents(QEventLoop::ExcludeUserInputEvents); qApp->setOverrideCursor(Qt::WaitCursor); PedGeometry *start_range = ped_geometry_new(device, current_start, 1); PedGeometry *end_range = ped_geometry_new(device, current_start, max_new_size); PedConstraint *constraint = ped_constraint_new(ped_alignment_new(0, 1), ped_alignment_new(-1, ONE_MIB), start_range, end_range, min_new_size, max_new_size); /* if constraint solves to nullptr then the new part will fail, so just bail out */ if (ped_constraint_solve_max(constraint) == nullptr) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); qApp->restoreOverrideCursor(); KMessageBox::error(nullptr, i18n("Partition extension failed")); ped_constraint_destroy(constraint); ped_geometry_destroy(start_range); ped_geometry_destroy(end_range); return false; } success = ped_disk_set_partition_geom(disk, getPedPartition(), constraint, max_start, max_end); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); qApp->restoreOverrideCursor(); ped_constraint_destroy(constraint); ped_geometry_destroy(start_range); ped_geometry_destroy(end_range); if (!success) { KMessageBox::error(nullptr, i18n("Partition extension failed")); return false; } else { // Here we wait for linux and udev to re-read the partition table before doing anything else. // Otherwise the resize program will fail. pedCommitAndWait(disk); if (is_pv) return pv_extend(getPath()); else if (fs_can_extend(fs, m_old_storage_part->isMounted())) return fs_extend(getPath(), fs, m_old_storage_part->getMountPoints()); else return true; } } bool PartitionChangeDialog::movePartition() { hide(); PedDisk *const disk = getPedPartition()->disk; PedDevice *const device = disk->dev; const PedSector ONE_MIB = 0x100000 / getSectorSize(); // sectors per megabyte const PedSector max_start = getMaxStart(); const PedSector max_end = getMaxEnd(); const PedSector current_size = getCurrentSize(); PedSector current_start = getCurrentStart(); PedSector new_start = max_start + getNewOffset(); // don't move if the move is less than 1 megabyte // and check that we have at least 1 meg to spare if (fabs(current_start - new_start) < ONE_MIB) return true; // pretend we moved since it wasn't worth doing else if (new_start < current_start) { // moving left if ((current_start - max_start) < ONE_MIB) return false; else if ((new_start - max_start) < (ONE_MIB / 2)) new_start = max_start + (ONE_MIB / 2); } else { // moving right if ((max_end - (current_start + current_size - 1)) < ONE_MIB) return false; else if ((new_start + current_size - 1) > (max_end - ONE_MIB / 2)) new_start = 2 + max_end - ((ONE_MIB / 2) + current_size); } qApp->processEvents(QEventLoop::ExcludeUserInputEvents); qApp->setOverrideCursor(Qt::WaitCursor); PedAlignment *start_alignment = ped_alignment_new(0, ONE_MIB); PedAlignment *end_alignment = ped_alignment_new(0, 1); PedGeometry *start_range = ped_geometry_new(device, new_start - (ONE_MIB / 2), ONE_MIB); PedGeometry *end_range = ped_geometry_new(device, new_start + (current_size - 1) - (ONE_MIB / 2), ONE_MIB); PedConstraint *constraint_1MiB = ped_constraint_new(start_alignment, end_alignment, start_range, end_range, current_size, current_size); PedSector old_start = current_start; PedSector old_size = current_size; int success = 0; if (constraint_1MiB) { success = ped_disk_set_partition_geom(disk, getPedPartition(), constraint_1MiB, max_start, max_end); } ped_exception_set_handler(my_handler); current_start = getPedPartition()->geom.start; // getCurrentStart() won't return the correct value here qApp->restoreOverrideCursor(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); ped_constraint_destroy(constraint_1MiB); ped_alignment_destroy(start_alignment); ped_alignment_destroy(end_alignment); ped_geometry_destroy(start_range); ped_geometry_destroy(end_range); if (!success) { KMessageBox::error(nullptr, i18n("Repartitioning failed: data not moved")); return false; } else { pedCommitAndWait(disk); return(movefs(old_start, current_start, old_size)); } } bool PartitionChangeDialog::bailout() { return m_bailout; } kvpm-0.9.10/kvpm/devicepropertiesstack.cpp0000644000175000017500000000430412770324126021072 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "devicepropertiesstack.h" #include "deviceproperties.h" #include "devicetree.h" #include "storagedevice.h" #include "storagepartition.h" #include #include #include /* This stack widget simply displays some information about the drive or device selected in the tree view. If nothing is selected an empty widget is used. */ DevicePropertiesStack::DevicePropertiesStack(QWidget *parent) : QStackedWidget(parent) { addWidget(getDefaultWidget()); setCurrentIndex(0); } void DevicePropertiesStack::changeDeviceStackIndex(QTreeWidgetItem *item) { setCurrentIndex(0); if (!item) return; const QString device_path = item->data(0, Qt::DisplayRole).toString(); const int list_size = m_device_path_list.size(); for (int x = 0; x < list_size; ++x) { if (m_device_path_list[x] == device_path) setCurrentIndex(x); } } void DevicePropertiesStack::loadData(QList devices) { QWidget *stackWidget; m_device_path_list.clear(); for (int x = count() - 1; x >= 0; --x) { // delete old member widgets stackWidget = widget(x); removeWidget(stackWidget); delete stackWidget; } for (auto dev : devices) { m_device_path_list.append(dev->getName()); addWidget(new DeviceProperties(dev)); for (auto part : dev->getStoragePartitions()) { m_device_path_list << part->getName(); addWidget(new DeviceProperties(part)); } } addWidget(getDefaultWidget()); setCurrentIndex(0); } QWidget *DevicePropertiesStack::getDefaultWidget() { QWidget *dw = new QWidget(); QHBoxLayout *layout = new QHBoxLayout(); layout->addWidget(new QLabel("Random String For The Layout")); dw->setLayout(layout); return dw; } kvpm-0.9.10/kvpm/vgmerge.h0000644000175000017500000000172112770324126015571 0ustar benscottbenscott/* * * * Copyright (C) 2010, 2011, 2012, 2013, 2014 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGMERGE_H #define VGMERGE_H #include #include #include "kvpmdialog.h" class QComboBox; class QCheckBox; class QStackedWidget; class VolGroup; class VGMergeDialog : public KvpmDialog { Q_OBJECT VolGroup *m_vg; QComboBox *m_target_combo; QCheckBox *m_autobackup; QStackedWidget *m_error_stack; QList m_extent_size; void checkSanity(); public: explicit VGMergeDialog(VolGroup *const volumeGroup, QWidget *parent = nullptr); private slots: void commit(); void compareExtentSize(); }; #endif kvpm-0.9.10/kvpm/pvgroupbox.cpp0000644000175000017500000003107012770324126016703 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2010, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "pvgroupbox.h" #include "misc.h" #include "physvol.h" #include "storagebase.h" #include "volgroup.h" #include #include #include namespace { bool isLessThan(const StorageBase *const dev1 ,const StorageBase *const dev2) { return dev1->getName() < dev2->getName(); } } PvGroupBox::PvGroupBox(QList> spaceList, AllocationPolicy policy, AllocationPolicy vgpolicy, bool target, QWidget *parent) : QGroupBox(parent), m_target(target) { for (auto space : spaceList) { m_pvs.append(space->pv); m_normal.append(space->normal); m_contiguous.append(space->contiguous); } if (m_target) setTitle(i18n("Target Physical Volumes")); else setTitle(i18n("Available Physical Volumes")); QGridLayout *const layout = new QGridLayout(); setLayout(layout); NoMungeCheck *check; const int pv_check_count = m_pvs.size(); m_space_label = new QLabel; m_extents_label = new QLabel; m_dialect = getDialect(); if (pv_check_count < 1) { m_extent_size = 1; QLabel *pv_label = new QLabel(i18n("No suitable volumes found")); layout->addWidget(pv_label); } else if (pv_check_count < 2) { m_extent_size = m_pvs[0]->getVg()->getExtentSize(); QLabel *pv_label = new QLabel(m_pvs[0]->getMapperName() + " " + KFormat().formatByteSize(m_pvs[0]->getRemaining(), 1, m_dialect)); layout->addWidget(pv_label, 0, 0, 1, -1); addLabelsAndButtons(layout, pv_check_count, policy, vgpolicy); calculateSpace(); } else { m_extent_size = m_pvs[0]->getVg()->getExtentSize(); for (int x = 0; x < pv_check_count; x++) { check = new NoMungeCheck(m_pvs[x]->getMapperName() + " " + KFormat().formatByteSize(m_pvs[x]->getRemaining(), 1, m_dialect)); check->setAlternateText(m_pvs[x]->getMapperName()); m_pv_checks.append(check); if (pv_check_count < 11) layout->addWidget(m_pv_checks[x], x % 5, x / 5); else if (pv_check_count % 3 == 0) layout->addWidget(m_pv_checks[x], x % (pv_check_count / 3), x / (pv_check_count / 3)); else layout->addWidget(m_pv_checks[x], x % ((pv_check_count + 2) / 3), x / ((pv_check_count + 2) / 3)); connect(check, SIGNAL(clicked(bool)), this, SLOT(calculateSpace())); } addLabelsAndButtons(layout, pv_check_count, policy, vgpolicy); } setChecksToPolicy(); } PvGroupBox::PvGroupBox(const QList devices, const long long extentSize, QWidget *parent) : QGroupBox(parent), m_devices(devices), m_extent_size(extentSize) { if (devices.size() > 1) setTitle(i18n("Available Physical Volumes")); else setTitle(i18n("Physical Volume")); QGridLayout *const layout = new QGridLayout(); setLayout(layout); QString name; long long size; int dev_count = 0; NoMungeCheck *check; qSort(m_devices.begin(), m_devices.end(), isLessThan); const int pv_check_count = m_devices.size(); m_space_label = new QLabel; m_extents_label = new QLabel; m_dialect = getDialect(); if (pv_check_count < 1) { QLabel *pv_label = new QLabel(i18n("none found")); layout->addWidget(pv_label); } else if (pv_check_count < 2) { name = m_devices[0]->getName(); size = m_devices[0]->getSize(); QLabel *pv_label = new QLabel(name + " " + KFormat().formatByteSize(size, 1, m_dialect)); layout->addWidget(pv_label, 0, 0, 1, -1); addLabelsAndButtons(layout, pv_check_count, NO_POLICY, NO_POLICY); calculateSpace(); } else { for (int x = 0; x < m_devices.size(); x++) { dev_count++; name = m_devices[x]->getName(); size = m_devices[x]->getSize(); check = new NoMungeCheck(name + " " + KFormat().formatByteSize(size, 1, m_dialect)); check->setAlternateText(name); check->setData(QVariant(size)); m_pv_checks.append(check); if (pv_check_count < 11) layout->addWidget(m_pv_checks[x], x % 5, x / 5); else if (pv_check_count % 3 == 0) layout->addWidget(m_pv_checks[x], x % (pv_check_count / 3), x / (pv_check_count / 3)); else layout->addWidget(m_pv_checks[x], x % ((pv_check_count + 2) / 3), x / ((pv_check_count + 2) / 3)); connect(check, SIGNAL(clicked(bool)), this, SLOT(calculateSpace())); } addLabelsAndButtons(layout, pv_check_count, NO_POLICY, NO_POLICY); setExtentSize(extentSize); } } QStringList PvGroupBox::getNames() { QStringList names; if (m_pv_checks.size()) { for (auto check : m_pv_checks) { if (check->isChecked()) names << check->getAlternateText(); } } else if (m_pvs.size()) { names << m_pvs[0]->getMapperName(); } else if (m_devices.size()) { names << m_devices[0]->getName(); } return names; } QStringList PvGroupBox::getAllNames() { QStringList names; if (m_pv_checks.size()) { for (auto check : m_pv_checks) { names << check->getAlternateText(); } } else if (m_pvs.size()) { names << m_pvs[0]->getMapperName(); } else if (m_devices.size()) { names << m_devices[0]->getName(); } return names; } long long PvGroupBox::getRemainingSpace() { long long space = 0; if (m_pv_checks.size()) { for (auto check : m_pv_checks) { if (check->isChecked()) space += (check->getData()).toLongLong(); } } else if (m_pvs.size()) { space = m_pvs[0]->getRemaining(); } else if (m_devices.size()) { space = m_devices[0]->getSize(); } else { space = 0; } return space; } long long PvGroupBox::getLargestSelectedSpace() { long long largest = 0; long long space = 0; if (!m_pvs.isEmpty()) { if (m_pv_checks.size()) { for (auto check : m_pv_checks) { space = check->getData().toLongLong(); if (check->isChecked() && (space > largest)) largest = space; } } else { largest = m_pvs[0]->getContiguous(); } } return largest; } QList PvGroupBox::getRemainingSpaceList() { QList space; if (m_pv_checks.size()) { for (auto check : m_pv_checks) { if (check->isChecked()) space.append((check->getData()).toLongLong()); } } else if (m_pvs.size()) { space.append(m_pvs[0]->getRemaining()); } else if (m_devices.size()) { space.append(m_devices[0]->getSize()); } return space; } void PvGroupBox::selectAll() { for (auto check : m_pv_checks) { if (check->isEnabled()) check->setChecked(true); } calculateSpace(); return; } void PvGroupBox::selectNone() { for (auto check : m_pv_checks) check->setChecked(false); calculateSpace(); return; } void PvGroupBox::calculateSpace() { if ((getEffectivePolicy() == CONTIGUOUS) && m_target) { m_space_label->setText(i18n("Contiguous space: %1", KFormat().formatByteSize(getLargestSelectedSpace(), 1, m_dialect))); m_extents_label->setText(i18n("Contiguous extents: %1", getLargestSelectedSpace() / m_extent_size)); } else { m_space_label->setText(i18n("Space: %1", KFormat().formatByteSize(getRemainingSpace(), 1, m_dialect))); m_extents_label->setText(i18n("Extents: %1", getRemainingSpace() / m_extent_size)); } emit stateChanged(); return; } void PvGroupBox::setExtentSize(long long extentSize) { m_extent_size = (extentSize >= 1024) ? extentSize : 1024; if (m_pv_checks.size()) { for (int x = 0; x < m_pv_checks.size(); ++x) { if ((m_pv_checks[x]->getData()).toLongLong() > (m_extent_size + 0xfffffLL)) // 1 MiB for MDA, fix this m_pv_checks[x]->setEnabled(true); // when MDA size is put in else { // liblvm2app m_pv_checks[x]->setChecked(false); m_pv_checks[x]->setEnabled(false); } } } calculateSpace(); } // Disable checkboxes that cant be used under the current settings // such as the allocation policy that is selected. void PvGroupBox::disableChecks(QStringList pvs) { for(auto check : m_pv_checks) check->setEnabled(true); if (!pvs.isEmpty() && !m_pv_checks.isEmpty()) { for (auto disable_name : pvs) { for (int x = 0; x < m_pvs.size(); ++x) { if (m_pvs[x]->getMapperName() == disable_name) { m_pv_checks[x]->setChecked(false); m_pv_checks[x]->setEnabled(false); } } } } } QHBoxLayout *PvGroupBox::getButtons() { QHBoxLayout *const layout = new QHBoxLayout(); QPushButton *const all = new QPushButton(i18n("Select all")); QPushButton *const none = new QPushButton(i18n("Clear all")); layout->addStretch(); layout->addWidget(all); layout->addStretch(); layout->addWidget(none); layout->addStretch(); connect(all, SIGNAL(clicked(bool)), this, SLOT(selectAll())); connect(none, SIGNAL(clicked(bool)), this, SLOT(selectNone())); return layout; } void PvGroupBox::addLabelsAndButtons(QGridLayout *layout, int pvCount, AllocationPolicy policy, AllocationPolicy vgpolicy) { QVBoxLayout *const spacer1 = new QVBoxLayout; QVBoxLayout *const spacer2 = new QVBoxLayout; spacer1->addSpacing(10); spacer2->addSpacing(10); const int count = layout->rowCount(); layout->addLayout(spacer1, count, 0, 1, -1); layout->addWidget(m_space_label, count + 1, 0, 1, -1); layout->addWidget(m_extents_label, count + 2, 0, 1, -1); layout->addLayout(spacer2, count + 3, 0, 1, -1); if (pvCount > 1) layout->addLayout(getButtons(), count + 4, 0, 1, -1); m_policy_combo = new PolicyComboBox(policy, vgpolicy); connect(m_policy_combo, SIGNAL(policyChanged(AllocationPolicy)), this, SLOT(setChecksToPolicy())); QHBoxLayout *const policy_layout = new QHBoxLayout; policy_layout->addStretch(); policy_layout->addWidget(m_policy_combo); policy_layout->addStretch(); layout->addLayout(policy_layout, count + 5, 0, 1, -1); if(policy == NO_POLICY) m_policy_combo->hide(); selectAll(); } AllocationPolicy PvGroupBox::getPolicy() { if (m_policy_combo == nullptr) return NORMAL; else return m_policy_combo->getPolicy(); } AllocationPolicy PvGroupBox::getEffectivePolicy() { if (m_policy_combo == nullptr) return NORMAL; else return m_policy_combo->getEffectivePolicy(); } void PvGroupBox::setChecksToPolicy() { if (!m_pvs.isEmpty() && m_policy_combo != nullptr) { const AllocationPolicy policy = getEffectivePolicy(); for (int x = 0; x < m_pv_checks.size(); ++x) { if (policy == CONTIGUOUS) { m_pv_checks[x]->setText(m_pvs[x]->getMapperName() + " " + KFormat().formatByteSize(m_contiguous[x], 1, m_dialect)); m_pv_checks[x]->setData(QVariant(m_contiguous[x])); } else { m_pv_checks[x]->setText(m_pvs[x]->getMapperName() + " " + KFormat().formatByteSize(m_normal[x], 1, m_dialect)); m_pv_checks[x]->setData(QVariant(m_normal[x])); } } } calculateSpace(); } KFormat::BinaryUnitDialect PvGroupBox::getDialect() { bool use_si_units; KConfigSkeleton skeleton; skeleton.setCurrentGroup("General"); skeleton.addItemBool("use_si_units", use_si_units, false); KFormat::BinaryUnitDialect dialect; if (use_si_units) dialect = KFormat::MetricBinaryDialect; else dialect = KFormat::IECBinaryDialect; return dialect; } kvpm-0.9.10/kvpm/vgwarning.h0000644000175000017500000000143312770324126016137 0ustar benscottbenscott/* * * * Copyright (C) 2013 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef VGWARNING_H #define VGWARNING_H #include class QVBoxLayout; class VolGroup; class VGWarning : public QWidget { QVBoxLayout *m_layout = nullptr; QWidget *buildPartialWarning(); QWidget *buildOpenFailedWarning(VolGroup *const group); QWidget *buildExportedNotice(); public: explicit VGWarning(QWidget *parent = nullptr); void loadMessage(VolGroup *const group); }; #endif kvpm-0.9.10/kvpm/mountentry.h0000644000175000017500000000332312770324126016361 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012, 2013, 2016 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef MOUNTENTRY_H #define MOUNTENTRY_H #include class QString; class mntent; class MountEntry : public QObject { Q_OBJECT QString m_device_name, // for example: "/dev/sda1" m_mount_point, m_filesystem_type, // ext3, reiserfs, swap etcetera m_mount_options, // options (per mount), such as "noatime," set when mounting a filesystem m_super_options; // options (per superblock) set when mounting a filesystem int m_mount_position; // More than on device may be mounted on a mount point. // This number is zero if nothing else is mounted on // this mount point. Otherwise numbers go in reverse // of mount order. 1 is the *last* one mounted, highest // number is the first one mounted. int m_major, m_minor; public: explicit MountEntry(MountEntry *const copy, QObject *parent = 0); explicit MountEntry(mntent *const entry, QObject *parent = 0); ~MountEntry(); MountEntry(QString const mountinfo, const int major, const int minor, QObject *parent = 0); QString getDeviceName(); QString getMountPoint(); QString getFilesystemType(); QString getMountOptions(); int getMountPosition(); int getMajorNumber(); int getMinorNumber(); void setMountPosition(const int pos); }; #endif kvpm-0.9.10/kvpm/devicesizechart.cpp0000644000175000017500000001132712770324126017647 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2009, 2011, 2012, 2013, 2014, 2016 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #include "devicesizechart.h" #include "devicesizechartseg.h" #include "devicetree.h" #include "storagedevice.h" #include "storagepartition.h" #include #include #include #include #include DeviceSizeChart::DeviceSizeChart(QWidget *const parent) : QFrame(parent) { m_layout = new QHBoxLayout(); m_layout->setSpacing(0); m_layout->setMargin(0); m_layout->setSizeConstraint(QLayout::SetNoConstraint); setLayout(m_layout); setMinimumHeight(45); setMaximumHeight(45); } void DeviceSizeChart::setNewDevice(QTreeWidgetItem *deviceItem) { if (deviceItem == nullptr) return; // Find the deviceItem at the top, just under the tree root. const QTreeWidget *const root = deviceItem->treeWidget(); while ((deviceItem->parent()) && (reinterpret_cast(deviceItem->parent()) != root)) deviceItem = deviceItem->parent(); for (int i = m_layout->count() - 1; i >= 0; --i) // delete all the children m_layout->takeAt(i)->widget()->deleteLater(); m_segments.clear(); m_ratios.clear(); m_extended_segments.clear(); m_extended_ratios.clear(); const StorageDevice *const device = static_cast((deviceItem->data(1, Qt::UserRole).value())); QWidget *segment = nullptr; if (deviceItem->childCount()) { // device with one or more partitions for (int i = 0; i < deviceItem->childCount(); ++i) { QTreeWidgetItem *const part_item = deviceItem->child(i); const StoragePartition *part = static_cast((part_item->data(0, Qt::UserRole)).value()); segment = new DeviceChartSeg(part_item); m_segments.append(segment); m_ratios.append(part->getSize() / static_cast(device->getSize())); connect(segment, SIGNAL(deviceMenuRequested(QTreeWidgetItem *)), this, SIGNAL(deviceMenuRequested(QTreeWidgetItem *))); if (part->getPedType() & PED_PARTITION_EXTENDED) { m_extended_layout = new QHBoxLayout(); m_extended_layout->setSpacing(0); m_extended_layout->setMargin(0); m_extended_layout->setSizeConstraint(QLayout::SetNoConstraint); if (!part->isEmptyExtended()) { for (int j = 0; j < part_item->childCount(); ++j) { QTreeWidgetItem *const extended_item = part_item->child(j); QWidget *const extended_segment = new DeviceChartSeg(extended_item); part = static_cast((extended_item->data(0, Qt::UserRole)).value()); m_extended_segments.append(extended_segment); m_extended_ratios.append(part->getSize() / static_cast(device->getSize())); m_extended_layout->addWidget(extended_segment); connect(extended_segment, SIGNAL(deviceMenuRequested(QTreeWidgetItem *)), this, SIGNAL(deviceMenuRequested(QTreeWidgetItem *))); } } segment->setLayout(m_extended_layout); } m_layout->addWidget(segment); } } else { segment = new DeviceChartSeg(deviceItem); m_segments.append(segment); m_ratios.append(1.0); m_layout->addWidget(segment); connect(segment, SIGNAL(deviceMenuRequested(QTreeWidgetItem *)), this, SIGNAL(deviceMenuRequested(QTreeWidgetItem *))); } resizeSegments(width()); } void DeviceSizeChart::resizeSegments(const int width) { int max_width; for (int i = m_segments.size() - 1 ; i >= 0; --i) { max_width = (width * m_ratios[i]) - 2; if (max_width < 1) max_width = 1; m_segments[i]->setMaximumWidth(max_width); } for (int i = m_extended_segments.size() - 1; i >= 0; --i) { max_width = (width * m_extended_ratios[i]) - 2; if (max_width < 1) max_width = 1; m_extended_segments[i]->setMaximumWidth(max_width); } } void DeviceSizeChart::resizeEvent(QResizeEvent *const event) { resizeSegments(event->size().width()); } kvpm-0.9.10/kvpm/snapmerge.h0000644000175000017500000000074012770324126016116 0ustar benscottbenscott/* * * * Copyright (C) 2011 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef SNAPMERGE_H #define SNAPMERGE_H class LogVol; bool merge_snap(LogVol *const snapshot); #endif kvpm-0.9.10/kvpm/lvproperties.h0000644000175000017500000000153312770324126016674 0ustar benscottbenscott/* * * * Copyright (C) 2008, 2011, 2012 Benjamin Scott * * This file is part of the Kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef LVPROPERTIES_H #define LVPROPERTIES_H #include class LogVol; class LVProperties : public QWidget { bool m_use_si_units; LogVol *m_lv; QFrame *mountPointsFrame(); QFrame *uuidFrame(); QFrame *fsFrame(const bool showFsUuid, const bool showFsLabel); QFrame *generalFrame(int segment); QFrame *physicalVolumesFrame(int segment); public: LVProperties(LogVol *const volume, const int segment, QWidget *parent = 0); }; #endif kvpm-0.9.10/kvpm/fsblocksize.h0000644000175000017500000000074612770324126016461 0ustar benscottbenscott/* * * * Copyright (C) 2009, 2012 Benjamin Scott * * This file is part of the kvpm project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3, as * published by the Free Software Foundation. * * See the file "COPYING" for the exact licensing terms. */ #ifndef FSBLOCKSIZE_H #define FSBLOCKSIZE_H class QString; long get_fs_block_size(QString path); #endif kvpm-0.9.10/images/0000755000175000017500000000000012770324126014253 5ustar benscottbenscottkvpm-0.9.10/images/CMakeLists.txt0000644000175000017500000000010612770324126017010 0ustar benscottbenscottinstall(FILES splash.png DESTINATION ${DATA_INSTALL_DIR}/kvpm/images )kvpm-0.9.10/images/splash.png0000644000175000017500000002434412770324126016262 0ustar benscottbenscottPNG  IHDRJTƊsRGBbKGD pHYs  tIME  ̣tEXtCommentCreated with GIMPW IDATx^]TOޗeq)KsAE 5E3/y3ĒƊ+;*UiR,˖>ww,܏e~~{N1Y`Jʧ6ڀ8_ `|b ~ƣh |Jٰ@ kS2`5*k@#_N ׋fnjBsK mm& P^Vke֎b70w5#88D!wNߝxB"+Ew{;| aRU>`0GokĨ8H pP(1짜p|>#H )دɉ͛ch¯ N = "'L zlle밋8rJTVjKj2c|"`Gc'[O:M:61.v/^}MnHLO5H"I 6JXnBS&C$`bݞR]8X)56n2 -@̀6:?i0du+:mfP7qX:p +* 9U[dj!ںsJRf̟mB5׸n]"Mb?t>=}!Y55-ޫr/>x!0J2;rY+qY0&Wt^YdOV+ޮ't=ѧ;}xE 7nOb0//}x)t_:6'vuFYW~vmo@cc m. q1.| +-[{^'? ߍ߽|F8LjVc3̀s^~Ymeڊf? v +ŻqgѮڟy _}xMCڴ)Ov/B9Ӎ5k QYIotbQ):<~{/"^?>g{0yrV>D2gtҜs׷sQ5 ݞ0\m!{CS?Iq#!r+O{IFt 0.M_`3z("@R.,at``A`H>^pp.D 7@0^?lH.N?]Y Y^q(ٳ7-?IX{alª_w]k1bC)fABii'E,g!1`'OGYQ. ܭN }=!dz8dzyćGVaŗc_XhBuF0& )VgQu"& UO=qg‘G9;B>?tcO9J}0M7S,iH!>oX]UՂCi~VPd]ONWL¼y9D ?}8M wjLɥE?]wyѯ_ Zp.˜9>يf]N,[[ .q?= i8dvkE8D%캌>w CRf϶s2[E!a|MlX_fq1Ecw-U<\/HsRHmB vp[?|xe?,)$bd$*ӟJH  +ǣqfPl#F8Ə~d+cR~g)꥗1cF|ݐ+pmsA#WR|Їo /w~vRTS'p㩧>ĭ8^Lj/Go6,Wjs Q]=Oy|~.^{,\B6b4 *Kr`U{.$"e6>A2l UkHm^R\ `*^d$$<]s睌A\D_zկ< "BR;Ϊ)A9L{4ʓ᯹ND5+'=ذP )o{Nq=y$݉I)f|9-p˽0lH?SjG e\K#㧍0[@f\q/QY ZV6)"*"LJ%ݎՏ^WʊKڪkhskisFQ{tRa&sL*֯~PʓOZ#YwՐMOVO[1VbEwv<"̺b4҄n]~fͲ+)56I~[ Q)S2bcM=i]Z#t(-D:XȢ:=c?|ґܰ)],%sq:SM AMT>ΓNJfFNU)/M7MCCl+V_Wp5mZzis !RUe J#}p)oAc,㣙/#;z F#}.RWvSS 8E,T:iWTژT>yN]#Q"GD{jjgUw&~.H6۷^KK_O|Q݋?ݓ_C"IxEKG~ZpnU1Z,V%kؿ8 +gG)Smj;}Jvȯ>XWdD]V|&[2p2R͞FQ_5+hUB:E\E};UdEt0r^zH.* 7puy]@t畕&lV߉aH*\QӼқ[p oiFھj7C@pY>hExD\E4@P Tn%r@i%K/5d]]C]&E ƍij "(IJKK}Ek;Tg>~  W/Mb#JH)%%&rO;T},m͐"پ*)q}{HK9/j^ŷ'J2O6.Y?¸:ҍI8&r1efv}-|d;H'a|(?5_{ENNlUMEdw-y.nZu]9xc;sh@s9OVd_hc͚妦C8 #Yɮ+~fS&}Y~*-jLU#Y9hR"}u'[>}Ir>6% GyezRvE_ ^rv 'sq6'*ȏѦAtpal`j.؆ŋ UxE>ܕ+C1 S"q^"ҥR2c $ ^'+?yrP[kR+饗S"MvsߐGĵa`3E,aX qb?ot}6e⋠T^>Ix ([~% +:Մ;WNN"!&Kb!>âQ!F.GA! PV^bTVb#?Z?aAec_1 qgر4y|M[yΦ`X[4貪w j]S"DK*7~ax݃ v_sM8}Ls#H=LA|{F4ѡT ;Hȱc7~ \HHrKsp^O >*w~^:yhjۦ]p>BEd# 8<My7;B&Cw^;M-%֝-L$Sx8se'AyL?+P60-xk.=L2E1 @y~%…(8er+6 6"R8{1Nv݆C\P(ӷHvʣNDB0Ϫa^ ;a0.crR! ۄpf2J PܗzاfĐZBt>.g+h7o ;+eYVE/h$(.r7cph:iBW2[oh~9v!*f5+.cZ"%19gq&Uܵ~5&36:ܩ+'9KK[Tkm玽D3áb6%E0vɺ/(5Ubr5oO֋5oS]B H:~)qxYZ{ ,}W͊by-9A,ň1W!Bh""&:\!=]q"!vnCxOUfAιSimj)p'f]|>n 5;@.z%6:OYTb B1f|)'{Ð`G?g13m}Yswk矏Ob%d(Ou5z@6j/f7A|8@]! V_$ڱvVCcp]5To"r;02 q5jҁ>@ # ((00ԗ'L]N@?iJX{cXFM-F*!-t;6 0z%xW^VcnrbJb/~?uN[lHHzl^~Xz+e^V/.su~An!"e%CjH0 xeV} rKȦH/’ɖ B))nXh!,s.@q ꐿ%柕^ +-\q  s0=|ᤓ*\g=u|e.8NכT0w `4M ы/Dޚ%$)mWU Aȹ>_ɴ!m(ʉwXQ )Ft eǓ<SILì "$g .d T!?<mXZba%f0[_ĝwa[i`53m*7cĺG;} D?Nζ*. ҫ~4Ve'ZȂs ob-Ol'~JjԎ1WD:1mCsUo`у0dHdQ8X+oљ-spTܒ&XU!<9cʔodL!R5dՋd:dJg T!%D"?'D3omB%hk{Ifz)H*tGk!6,YecQR>AcYiHO'(X5H$PFA z\ܕ3? jq.i&dsR$6J:9ħiݺB#I:,ܐ(?(:%?pEϏ'K8#m@R͙kܹ~t$^O եmVU{!fqeuoʯʒ~ 53hlH2tjÐlI@&;=jÒ e%ᆣ~'W^CG7DlgcN>okI%?^&N / U2FO[Zi,W SfLţ[]~ 4$]ښ`yv,ZU#"9.<" 0.Bث์)@U2t()*Κqw}Zq̱3pauʛp1w}4yc'1sf|{>GݜcD3IDATh:ǕXRm'){Nj5duCE/ea睷֣0#O_/y:^#ȮV_! ly 0q090&Z Æs'j1u;n$sMh/ec.KTJTK&2S[KkCx)?N8t/JO{Tm9]wbo.)q-񓏆P\>Lz W<.mDxrDzL$a؆5T";LF>aqq͍MIoMΘ~$f}<݉`W@;*{U2Dy0 ӧǑ3o//@fRsbfx/,>2}̙m3L"J6q *5AJ DJKن+Vyf}7svX򜒛ڨ]))f8\U_|3& CAq^)C{gpH $gI~1>Xy cW+5N-qVsͬk;v`{] c@mΚ>_y*d1ڌW*썦x_a>vtx{omsrK.$? /1J9Le#?`2{]7 2vZ|o9t?ު#GoĊA|l7ۤ#5WD[r\00_% }m1[41U/x59[ha'Ȩ7 / .U3f&Q(=_5h57q9fW44=(w:\&i\&v"׀7.SG:".` 0i56]wS_QFvn/6LYwlZ@f MB(_P5 ؉c(ć桚G:g >Eiu.&;TxwU j*ȦsV/v48e[6nݎ/Vm:(knnAIe\PH>mxT=F9 CFaC1Cm2 ։K7ۅ˗wnW%- zR[kr(k+taVTW BI(Th~.w!nWۜE/`A!=fW@,zV.X-v|kQE-]TF^=ru7a{nC/G"ېZ!3|qK ^_Σ?> Em]D|6(tjae?Ϸ0A~^ lXb)6>AAқA?BZw.ٟ ikd&jأݭ̀Z!u:iq .>*J hg8 Aa/I6^56ckj+G6zlf0fˣg"I;sH-,GQA\:0wTr$c|=}8ٶv4X1wj О/J=4g! <$(@B>߃=Ft C Z"ŀ=N4fa@@,jXP1 $0A4  ΀A$/xL `XP1 $RWnBIENDB`kvpm-0.9.10/README0000644000175000017500000000421512770324126013670 0ustar benscottbenscottKvpm is a KDE (version 4) GUI for the Linux Volume Manager project and libparted. It needs KDE 4 libraries and headers installed. It uses the standard lvm tools and programs to work with logical volumes. It also can format volumes and mount them. It is possible to create, delete, move or resize partitions. This program is currently being developed on lvm version (with library lvm2app): ~# lvs --version LVM version: 2.02.164(2) (2016-08-15) Library version: 1.02.133 (2016-08-15) Driver version: 4.34.0 Because kvpm relies on the exact format of the lvm program output and the presence of the library "lvm2app" it may not work correctly with earlier versions. udev and udevadm must be installed too. The following development headers need to be installed for compilation: lvm2app.h libdevmapper.h libparted version 3.2 or higher libblkid kdelibs version 5 with version 4 support The file: /proc/self/mountinfo must also be present. It was introduced in linux kernel version 2.6.26. NOTE: If you compile a new version of lvm and devmapper, libparted also links to libdevmapper and may need to recompiled. I have been using parted v2.3 but v3.0 should also work now. To compile the source just "cd" into the top directory, the one with "CHANGELOG," and type "cmake ." with the space and period after it. Next run "make" and "make install" if all goes well you will have a working executable. Translation files are in the "po" subdirectory. The icons in icons/local are from Silk: Silk icon set 1.3 _________________________________________ Mark James http://www.famfamfam.com/lab/icons/silk/ _________________________________________ This work is licensed under a Creative Commons Attribution 2.5 License[1]. [ http://creativecommons.org/licenses/by/2.5/ ] This means you may use it for any purpose, and make any changes you like. All I ask is that you include a link back to this page in your credits. Are you using this icon set? Send me an email (including a link or picture if available) to mjames@gmail.com Any other questions about this icon set please contact mjames@gmail.com [1] Also CCA license 3.0 on web site kvpm-0.9.10/COPYING0000644000175000017500000010451312770324126014045 0ustar benscottbenscott GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . kvpm-0.9.10/docbook/0000755000175000017500000000000012770324524014430 5ustar benscottbenscottkvpm-0.9.10/docbook/main.png0000644000175000017500000030415512770324126016070 0ustar benscottbenscottPNG  IHDR HsBIT|d pHYs+tEXtDescriptionWindow Class: kvpm tEXtTitlekvpm*;\ IDATxgֆߞ,qDTPQ+F |zkĜ#*AA1x1a1AX]Ɖ]ߏI:M<2t9gޭfE%d# # Wxƪ0Qyl{XrHtwWq+Sn +rj7bt_ r 6I s^ JgId:, ⟆bGUWd`dB-}a0b&MZciIr(H7MjfX' mbIVv|U$T+zv>H(K7ka]Y1ʷv vV=uY3`Tc<=bP'v.Ƃ?LR~23H7K7CA0?U- &ZIhn,I_ƀƒA{qP v-, `c$d@AQ_$$'֥lu\;̓n@lA4_`SJ^F:;+­]rD59b&%86őPgCqMFwIYfB]Ho"dLc76$U6mRIbwuԉc윜\r׶ ¶ ex<DQ0YFVv~[qdYF(B0DV999  c~A XV+/s̡m7dކRDO\8~3{&Wlq?ET\]~I:>$]>9-h崢131O*˵3ᄛ McIHC0 [[~:tt&p˦PWH$ԉ{p8|ɑ?DuߨBFPPPhE }^ػo<@4o$!*3x=c(ݵ ۶mCaadeeٯ$A?_Yq_84f^:r3}'5@b&073+yt:NQ6b*W XEB39 j0׋<`Slc>scUkB@d<+g3P&tPQܝm ^$I!f`#H5Ut.ȲC%6W LJP-cgi :o5kŸۣeU cAރC9,Ȳ j(Ix<Xh~ j >%-5w0Cds٣ %& VC6f%MJR⟃|&ۘ|, po 㙚5076 s$,,H'WD:FWa0_g+K`U ::WZKDIxhޢ%,sSj8t">fYv͸J|,>IñI~wD+@R ]┣P$xB,gBbsMY ,gK܌ljgĕ['CuF4B泔L;M G tmD:fqx)% 6MݪPg<AwQ q(YA$:Qeehּkh\u9/U?aʓOZm8 ۑը-o݉-˰9ڸmڊTΒlܴPH3QVVVZ!PSP(h TWeeFeÆ"EݕmalA57rRoc08??'L)?Dzl0R.Fʆ3ONcc8tKx,D/y~s(qůXxoht^.:^|Vյ*?cTN,n 23Oc+hYcQ0Em(=l[$D碢ކ7F)̰dJ&Z2sl`#nBHNcvTWW*m-HZSuYX-G/5^%`S 0k֋xqbuKWnDye 6l.ͻa.lZ [KQظ ڵmjf=C,Go_9  ^kVF(Tطҽ ǂK z,Q] cqVlI tVlN!N$$cB~\0'n1W%UNݵ fȁah -x^ F=~ObvK0Ѓ#qѳY_v3q0uJ]GFx~W00DQnpL]"nG l&w/invz莣݊w6+k~`N:><^pRg ^+01E8: Ap?{$աԹe/>?9_v0Ӵ&Ri&MbjFP731sPy,/4 7D>,n6TWUt@zNRF PNSv|Gųa%]bSsL1k蠦`Qr6Vz^z+3_y1vh#GP}0ychŀg[x3!t+( 1 1 ü;o7 !~X|h 2dUmay`+1-8yY [L=y1vd?Ku]h-OVg;UC:|r6oALK> X3>}ZJh1폰|F*eN,kc۫?0,tovߏG돾7 |C;+6 ]6I 9-9/] 4( yⲋG򱗣qB>>*+! Y׉8޺Sz Gu^EK;6<7Ypfϙn'D0zhHd;8x)ڂžhZliFWH4`ȇ())dI@Nnޗn. pDtbvV6vß _V݆ ch77;c4iVSRbS6w EH*Y8Tuގ(RPT $TtJӶMj+qC\ :^QI/8$>TC^eh@ lfLMPˀ1=n? яn#pi²btXj$^V &Gd9"C)B"K!ȒX|5_,ul ("9bm4U[KO>Ӧ\Ve0^w1]b僌H( 2VYB8B ɛwA$*ʉcECvɐL@#`2 yKMT$inZ'3}jKKl:qm։fڰK;#+a#^5Ĉ ʥ|VLV;.Ss&~ݹ{6|n_W;n+yTk , Ns^^f,l4]rysb3;W_O,VvV.ӷ\S> 7ΞE?DWX9VKV?f畕s{@)fbBxӜ$ (Y= \gLaxS?n `6`_jߗ H'ڳ oو/B(Ub['^XZi_scl rk ڛocb5Xvk*ρߗ7k aBNN6rs 77qc׋͚VnB𕓛~\<4o ^A+\lͩy /fB]Xc s N<],}<81 ETIv;YB``-ީl^L,r$̽ѷ-a4xNw*o`!D{vA8p]p#PME'0uYj$U$DG{=&~\ .VEJ;g^?عz:,>c~X&(hsO8߉θ鑡(00os>)_1~</a CNà~C1tMruGDe~'džֱO CC{¨.S'.BiQxj/Xh daΑ8gt@q$ l2?W}Nj<'Ro]0& Mt`V2|;nMg( oWg fLc(þ/A#hwY)\;w8҉xW ivyeFB!l߶JwbKUeAu@=Ѿy'l C`u]tL/z DTm_^F8B$BT";;l0=4 S]g[-/1hy(_iB;+ZE;Bp C[Nq3=>l#( ꭷ3 &qN_I^d! ɗY99!` [Y :# =X`Q! Bff[/"B!A,|ܶeG1cE7u%xez=" ʂ#LF4F$)yQA$ />DA%/(¡RUUi#Wn @Ql ėЉ+T *&vV㙊JqPSQarҤ>SbOrKX;9 IWHGds# uAO&uyVb`mKFѦy~|0sX[ 9:vt 0nۆ{AǎQTT` ;af4kݺwǁ!++Rl zB/)7;{_If W+V :mb]̪Y.epaԙg7T.姽> XHn8x^O; S^x_a0|1zP:2c8!H!W `WSY7ΒΑo :@KM.H'L;NPdx>׃: 2G&u۲XFꚙh]ƞP1p҃㑱fS9~_O?c/l7H$ݻ`x'*+*b猡Q#.B6Y&,;C'{3-YЊhgNLUK{Rq9 ~L<~i$xY@h|foΞs,-Zt N;3, $ze֥sVceP39Eg6#"zúL 2I&u۲X`SfT9Nh{&nƀEPTha!@ ‘|>"??YYY6. EsG,T],9vVE;]B]w|U]6.5pJK78 γ)08R\=ۈ?StM"]Zb^CcPG?LD=m p"ڹ1vLvVl;]BwT+:R2+9-r:՗$ATo@עLS+x3I xH!|O$(RfDf$ zO~gdʹb:n al 'ZjkC֮8I%.lExck}q% IDATXm겿t uAu/qN_Au@7.ٟ5Re.s&*4O7ci c-qtN0Ln/-ϺN3)$ik?ڶڪLdt;gTuQ}A5T#Z ȓuBNaU iN ilXeM~XjpehKeUj|t+ŠjEcԦMZ璎ط_[AhfAD 1Ѳ=`BS8Z4V8Z3/Mࣳ4r'Yr'I&L&R6$QLi9~α>0O{ov2î-{sa.feNa-6戨T'@g."Щl " /MbaMe. +tA  e{qx]-9vwv?uu4֚兜l ܩlTCW ҎbX.(uUfZ.fASHBO_s!OPwy=)* ŵ_عG\b(&!=Ͳ5hCK~D6Xp&i t5K,mM--վV'q$b2V˗-CUuu,ds p҉'^t! ^ T(Q[<7qT'+"N)s(i+KgiwqtroB/x y@9 ļYtqc֜_;ca.cXjGK/q*aG$ tB׆"Z[t9_ŶucHGտe+BWe{HGW]x<<̗gOOB"xq51@fۜόIUv,ǴwE W"VTf` 2 *~ھ-gCUƗA;A>]BhrA-8jjS\nVH} fl\JjD>ea 7m/4kx:X&718:љ|Il1Q4{Oĭ7 Vʏ8eO\18i*!ݸIVE7flow:ZV/?BTFxj{"pb]LLB^sR|$8J[5ݣ~W1 }g qSʫFPpp| nv+OıK|YoJ\ԯMq0@V2KET^p꛴^Jďxǩ~@Ӫ4%ԗr,r&f?U_AQ g[bE/Mcc85V ]Fv9Y`_@3H08-a5+HGHY-L&BZlE7&IZKͶSxZN~9vr58RPF\BӉv qYjTu8f-# N` v)Y'RH9NQ+lq0df>;/l,ީ&'iϾ ,l'\ "APa17!;H6Ҕ.ҌlI:D9MrI t&c}nys' ̑ x}e2bCuY<5bO<= 1)%ڥL%iqEWЖIJք\MĐeߙ1CIbADD-Ͷhfc9G^ w)+u;; q= >^XT&:sǏo6#OX%zD: L³.?ܷ+Hو8XT3T[5L*S.[~ qt:1g၄hL*D$ĮY\H$@krR\ nH̴Rlɬ (H a..!5/f˹ Jwj]"))K覚u}yHͶKy .%C0ʐǩuK88 .M@|G?[.ҏq^Op>)vbZKw5ۯ%{$6o!i[!uaM±~݋_7/,X5;kot88 >LwhRXgm#.F`;Kμyrh*?.Im7tIEނ퍫G4Bpxxh}~^8\mxrPU,E]@2RVIX;n0s@yijJRӶhV̢9&Zl qj {⛉薦` eE+]# !N`aK D9Ӳ)N9,/KtZ:q`\Al$)P5cBfjIqM#%Ҁa4qm⥰QJ<55Nřu'1p6mZbi->rGc@0*I192p`skQ͑ p |=˱YB8P. m|?_Sඊ@>mz:_. #: xLsNCt;H- 5ڛ8 kkI:p}L;UKLޠ@dpE< 1Ng fuf&ǍoW|[:7lHކ{ru͞58V:£uQN\9Ӛպ8gng)@GD!}Ȋ4 ŒRbBtK&?'fIKY)cI-.iuRpH,{M+-TFz)Z|Z|HI՗qO` QN_6V1qЮu3 o~-^v4z_F!c﷯ces0Cؾe<3C|q/"I#qȫ16kb3T<+Q6{ .{=-pذ+q'Mt=3s>B8 qh6¯Λxǎڗ/\WaI[ȼm'߱;ևK;gtKhoxYb[y@.Z_xx+}$[gX~ɕ^|?c},lK||vM˻ۢ+XgmS?}ZnK`: ~;P=ݍ/>ᖙsS .̇=Y1mz!ڎ />-PKI5؝xRx 1Q+QbrG|t0KX4KD=48&>[! f'WaݸQC0[O% PoyOQ5 eW#9SliQcxgل3P[(gQ0'714pKd-rCK#ʓ: j̐#X/]읩lc)I.W b*Mk7T*aqmȈ?{b]ϸg)L^|҇Ⱦl*t@LT~&G{s=Ҿ&G\} ם+{{\;R_5\9*.p@ުrޗuh(8n?(ְ0'k&`u3x2( ,.>m݋+ pj317v} Z}ovl9!94tzfm 3 6sp%L43qC[ ^&.FV3xz5)\喠(VieY*ymA3,:U)-m !T^?>!Sœis4Ĺ4$=] !u)AZ/(0^q/2M7'j2Xe7ѧm~ES.@^}qfK/>v…ĖAp駩#7^}G|ulOa65ؼ;*$dqRpo#5LsmtQ} Z!UOp҆VD1 tp EPlp.މ+c[9T&ZaQ.;3RVfSjUKŲ(q+)I-t z] -#etC3c|K%_C#9 X$`,ӜJi'1e]j<~jm,cM=ɺ4 `wuhKj7M1(FPb@9O5NUrܴY݄osw6VMG^aS,Y_nDžJFn~̻濘2޲(j޺+~MA u6v#-q&/UDv◅q|?ο_ǺJQn:G֕}vojcJ}Al-+Bac ;ltɅ ϻT;Aj~߼1YAz'V?S~hS}BsJo~hN ӉŔa] $J0,NǏO4 6EǸt/egZ ԃj(?T-/~X~g5(2U~sљ}ö1b-4;&XɘC<AD]{Iף}I//^y:?+bְur.#x1AZt=ܾ98o*Ń&G^7so%# /w3Oƨ#qK/GbjYR7ruuNpF~tv?^)^}}uk= C/0jml%N ~<'Fhu)G0}{bE[mFFp {DP~'߄wN/GѬso#x|pW$/%u)7_ V9xdBf<0Lمz _ӯYO1mCeW%1΅)>& ^j/V QU\(RpJS;7))_;FP*ٮ;! U_⦋pmD_&)?X9#dz;kҏi=r,42E{6Zʥ<evԺXdrdlͦgkS3Ȁ gœe19 G$Ƀ_#ѱI"]93LRu:q.&)$_\@VwL[+ҔLgvLk] 0l)_c ׋-jgfAGL=z M=yG5eڌ|B!@;H>b0.:rnug&.ጼTy*GNDɌ=])p8Lb};44_*ӡxf]Y#f݄ IDATf?QN!A0Nja.-I*A-)IbA7OLI).uI5H͘Kȓ~IN=k/SqNTϬŬ)jxĬn 겎8Rs_GC =fON(رgxc]` [ۊA`*53+M7@t >‹Elڈo04[i[̳0p:,=nCGtVYV?T..Mk q1Ȉk*8Y#%BK: )0,_y<XGA4,jKhEn y@bv"H[I !MqyYƂ$96rq&[9(. h$AG{xH\.6.'JͰSЊuJ;Xg:w;(X۴L#%ĻZe,#*3D2"rV%{:wFTsk: dTjGȋm+qr-ʂi {"4OS3qϠ@vSёs^ q2 (GYj|r[Ii,#.BEh'!IR\DM-ÑGGZG  dae/4VL,MzWB]Z6lײ' _dj$A- iD摣QBATUV&T`Ap,XlK_e6;OPXp0+ڬL-5otUi-S]E/>(GQo* -% #4tCYt;'B`mՉh`Ax$ m۵ú?~GJAD^֟j^*D17Z @"  #b]4Ş={PPjND$ qBٳzu]% .v=k'X ]I[Vڏ$  Pe550d.8djje:  nҼACYKA?K7I#LKAXpeI,AAKbc2d.v 'z-% pC"K{܎2uEx    בgLt݃z3/j[}ǐ.ÿqic2EᐣOĐsB߶~^9>w;=    /6 Lcu91kc|wgL㷢b"R7q[\T2+8'O"PU⍿ !MC#r0nvB0~"    ĵK3 _j;2\8*>cqoEݿEҾ LD^/rBޭЩq8k:νx?W    Z&LD+PM~cx4 \7` ap/f/·" ;YcfU}Οf_}/OO~AI^EMj|4YZVuHpPtMH2*ӟzZz:7mgFLĊyrC9ФN{?D[d3-ۅOq1h$q6s7ϸ=x^wL{;@Qw/̗Wk+p$} ƞ9^[wg\5g\<{ r9 2X    X3Tv8f,\Ap%@*8>z} V`+i8$MƵBQ0X8~p-~_&KtzBލ9.㪞>Ed9_č-CeOş'Fxޭ^0)g }hwh76/iF-sK bEPY|3)Pr vKY-Ƣ{B,l)>y6JkuAAAAicQc`ossFވ7-ǪM{h#bWhswR74›] +@i$؆G^O,I'?y9?0B^CqǠ`] `_:0f|6l-_٣'wAav6to:_YDxyv@꿱7l>;^+eU̙ s4ެFh{pD n႑УU>|^/۠q`c\:    XRY&z\ +NBckxd0 bVDỿpґUeТ IN8uz#>v3F ?'N:QZR @`3>N;߇ǟιl7m}vmAd࢖{ېj 瞍G&,~QV/7 /M s+iAAAAcuP-Dg뀸}h>j<}#OolOƾPzuy͑܋ń ocj+7n3ǮG8Or\;V4r Z[ e%0SᱺA e/-{XB9" s ^zp&>>[聼+\2>7 U 17=__3t.؁~.A04KgKBiUR[:?/fٝqRm;#'Hv4&msoλUaDɘC۔ C;GaWURl6ם3 5GG[[c渷>Y.ģgɻ5A'_kg>)fr1&>3?Q@ 0|O c?!-váN/#\kn8^x{ZtŅwxս7EfYr`L3n!&@KM(Iސr!!K J!@PC f4.6pS[IwWJV~gvggΜsfv淧x;!7_1'?НCxƷι{^8x;[6MX=w {ȃ4$""""""""[hhܶuzCQY'?[nk=bdK|v+}~{acS3rOav$"""""""K$Tڜ YNG-baQ.c'/?݉q2 3hFdqq?-"!!8a:0r.K4%5v$ITxֶ zt bfl1Ae8uDDDDD4lbb˽KN7!:K逪jk(fiV,!J["Uʹq8d.8Ԫ::l@0J!LYp8 3v: IBy& IDATE2DYhd hjf}LarvC4tttv!;r.2[y<ց|-sJ?G$봎]v'Mr躁/֬Ass`:6oق躎ZL4[z#t5xI+]nu`r'`e93N:""""" l͖*|}Gk{yt:QY^(iLTZA<ޒ .fI$!J!J1n̘N-DQ1gfh-Xabb8<d&v#EQ n݊1cR@SSRjjFkb>E0|^ƏW QrőN+k`U pP?$;SO?4R\.m+bIz:][(n7:::H$v3uers嶤3Iz!Ӓp}'Oe+k܌d2j(H 4q"\.TUŖ-[7$.Eʁ -[rvVu- ͖úrBv~&"""""*5E y@yJ: `0[uAD1 &/k,?y(Y7HP5VB[[[.Dl8(Ï?6~ Ȳtp[hoogWQQMW[v'Ӊ 躎h4-[vt:`>ӾصuVo?SZnw@4L4Df>EQ܌14 CS5t9耦% . C$ɐ0 f[zy^b1D#Q`<0t=Si6L>gөR$n7C!n<>/ ?B[[7Vv.|5dd2Iv@4`---/!BkQQYra%F*DeE&NzZsaеLUEh)s u JuC:$Kr[a+R˺p:Ʊڪe(x23:"n:9P嵵nfZ=R) `n۬9Sn2D8F*n;X*QS3rn$^XP:L8!dӡiEп1뺎AfvSNPeeAx=پmZ@ bj0kQ+GKGw f7BQ*TUĉ{oMӰrJqU4MØ1c0~x;ݖ>|rVށn\w9{Μ UUҌ>SjYgqoYY:^/$IE4ņ6" " @UU$ (*7֚X4M j=9wAs$2e,2XGDDDDD#J&Hb쟹R̛( sCr^M*oD1@7;MD Unu n]^ڬ:ͱ& @k8TZQB*֭[ǡɤ5P(p:톪hjj(+E_n:}ZЅBep͝kê{ƎI>r`)Pgrݘ;w.֯_-[ Y;u͜L?vvv1u_Y^z: En8o(BsK b]ߜ&!*m-J* |^/jjF<*j5K4|m)R>oG"""""(n:kٛIgQݵrW@Tܛ\!#u opt]GY0دRUe%Q^^nu5'm] 0PUUeZ7("ٞT`{c#QByy9*yAS5F&c8DQDY(9;FG{;t8z nYggc8-_q s0"޼g_̌ *D ZEQĴi0m4}1uuڶ R)k86lcd2i•biشitR?~s++IpتCZԌ5hu|6斁aH9lū(JlkP&"""""*EQY.v$NVTT૯Buu7H !H@NܛoYO$ł`(2sxظqc\Xtcƌ4 [nΜ)tɶ#JNA2I,+˴RU(jF6iuCuAݮ:w`uMR}u BQ q;"f{b1>$Igmtx<$ X,3!,}LypݺuXt)>`c=)}Vƶn{I:mTUŗcuVvsJ$wT׌ihljB[[^/L?b1qPQUٯN5Сi;2 Be1iHӈF~H:;"ua Z"""""zOS,h r[I7KfVn>Ś5Hc?n\auiEAk jvvKP:Bu,[3qF"QI8zq#z|Wc;s9Xt- ˡeRU!˒5H}mmmH]S3n] 挿tŬjku6enu5@z IJ^{MnӺnӜ 9  B?V{wvK_&w},c͎^ Vwخ.U}^/GU#ڊX,fEg**++P3jo*t==;Q9b8F4 B|&X""""""*EE @v҃l2s{EQaFh + MO$niV T*t: ǃ @cS3"t]jR_mGA+@e'bQƎC{G۷o+݊`붭PҊDuPt]<,KPAOc ))WCG0@uu5UxiC1YOA;=EQ2*U't ۅhLF$A0D{{{ֿ2躎v^Ϡn`hE.'Y˂A@*FcS5p` Oj ð&0'f$Ip\eMMMM шQԖu&]׭1rg54gj&J'L(p# K Tv-30fv% h}QfGjoohJ&ȞŐˤxnhD"&QxNa:\.' Mՠi*LBg%NȖyYY ]@+8c擯+lss3jkk5|5`Bo K$n}^ 烢(V55,NRyR@5k,0 |غukqP'CR{z1iӦ",y쨪T:eYeYnPUN~>(X̚hqus7f.H`ҤoG58hW NllWQ^k,AH$l+9;6R3 [ $ -À ,@&@:4\(auzvd χJk-iH&V>);]áͿ7oތD,WE\.A(jU̙eY&Dioo xƎ0hG 60l17oF$ҁQ'$Yp8(~?<#L;ju}r8pA 0`ӡiDQ[˾>Az]hCSr0zhx^vtQ еL90t:1t]G2B{{;:::0zh_7UnDDDDDDT`1ovEQD:઩'˗CWL81w QzP^B(TYDQ,?n$A@0(j_CV3%"C!i475!#HBȮSܖЏY'&Ohͷf(A (8rA- }1274 -PgEǚ]8O䌇c0\YY^>6[ԙIy(^k_(/0HD0gP mÀh$bkfBP_;$IBY0_vrb˅a'H%~/3YP~Ria~DQ]$i`KU""""" FșD6Etv{r|>>](dƨ@K2MS!hzlwրA!7=|N~x蚶c"|v#D"DX@Ŝa L!H@7-,jPȤK `G7`Amyfzk;ɷtOoO&[f꺎D"FUe堔,p2.LJMS %8xӧNm.DnkMqۃT2 -j̘SE@϶Jzj [ v (p9p9E_!Ǧ6P(//|+4OcK` QO$b>.I* bh0 (zq1yҤbO.T΢t?C!wJ$!ٖ)|,va0X``tןrFNg< '^Q|@8v%""""""""* 눈JuDDDDDDDDD%:""""""""`Q`D0XGDDDDDDDDT"d0\]_>p ޮQbq g52NK伖}"""k""""*5^v`7X""""""""R`c 눈JuDDDDDDDDD%:""""""""`Q`D0XGDDDDDDDDT"#""""""""* ـ1눈J<vݸM׋"""Zv&jYww+DDDDlԲn+DDDDld#ax}"""""""""njk8Q.DDDDDDDDDTq|8 =o@Dԣ? De]Xp}y  d^7|3g>|3g> uz2}ޗ66nG~@J À |3eb&@Lc/{\n1~dL8 ^oGDd]3^^sy?:!F)T񁮎(CF 1GJϋ'  ]ڈrAL?x] B]%C4-hy}>h:zvz刈z{M"iz:o5"*hP}O //ϼ +r_3:GwqBEaݿƁbWĕdyNEWM81q88n~v =(= |3)]1/mґh=o4p=gcǑ3,_){: ͝NS}o}]"nG,vR̗@\ǗsHa7gκx\i4<~?~רD/,sX/ag\jGn c GSOKlϸs"1=ֈ O,׬C;9غ,˺s0>׶u݇VgGȅ-ޮ[ÙX/x]\=\=-|CUKR{ IDAT@84 e_#(ĸ7@|sSě=rxuXh4 ?ɠbGjiλr̩Q\|C,V%0MT=LJS.mѭ/Ǵ*O9?s*ϑnyMƱ`^MΥRh {:3?"z˃uRᚌoKϬ?No&%<]:L ,#;n$ _6ɠA]8ތugǔ"ޖTv~^)q+/Oò(߽3 rc[S䦳7Z~zھ4zί]?.+~7v. ~C@]я';/cï~}0#X>c*'♋2]V(ٙW6ş.^gn64!Ze_?|'ys=J<{F݄,Y^i󾇟?uV ^]WI /~G:\\pL|&ؗ{_4#-0opO^ TzGϿ["7F6G|:N>nwL9/8V-~8ZYe\:3y7VY+V I};TG~>N?&sHB&OݷwϾ;{|h/c9»oxXpJO>7~g$gVxi ܗHF٪s ÝT|눠/҉jhW@B?c?;G|/5;BG㔪+ǯUsCC[\{pG;WpO@օ0֧ix/aAboO=f8q wrz&Ƒ'dzOèB3X u5vn}n>k=Acr(_pn=w|/?ƿ. ^v.1e{ {\j^#Pںͮ*hx!xEXn~M[ΛOdqÓp2lԅ\Z޻DǞCL {z=w܌ݽL7;?mWMCXc냚%Uz5x.~r/0 =ތMǫ _} 1i7i<[:XdXK7.-C w?o\K\xюM=;n[q>͵{Ͼb;|@,a_y M\=-xZz>3zV腭c2~?s88:ܩAO6O3Cbbkkn#'EgiF6K`c 87+.ɻ!*k񧣏­_ᾓ:5hylrHO/rLwOx1ff=]x_GœO8|cgW]r$jKbjbש! ?7eW8ѡގ>A)/z(D㣏cU==Ҁ=O? Q,Q4~qrCr1z#^<6>{"2E<Nh-pcr6u).9aO9 j q,~`9:.ځ-q?B彳q_n*8Qؘٛʖ.;T:BEIGAu0`@M`òŸwo_ڎYἹSQqS3 .ؓ_NCYXtH6BnSn)U^Ȳ3ŗ~~~= uᕸ=un8tؖadAˤ~}-:S|% @ 4_u =&m0?m֎-1?BDg6眍_S_VYϣ%PGl%?kN^{Ūwv>J<3υ*~}D83}k`w$?::"H&=/|O6S0ݝyM\x}#Hi˸ʛYϫ%u Ҍ/s7.?T8 d,jG.;eT5rT5[_o鳂36Հw׆pc{vKM,ҷ߂,A _+[^ CoZe~9АBɖph]i8e"p;hnBl`'1ezRE<;y.!|[K5&vsؿs"gCzEt#Z'D[u!{"=b\Fu|&I}Wk4#1#1sVp՘܈GS話aǤͼ0znTzg)!w4N :Z_u'.G`7鋝c2b(>l9DdTXU1 ǜ`$ ynް|۳K@j^ގ-]5_m#r}n3ئɨ;"s ;$HdS+| k65[8sgdZ~pLů~2 Vc×b"?5킌kW}4{$Pn뗝.(쉤aR?}?<#/AAzGxp60aJvZR^ =Cp\7.cxeo̫Z sH^Tx ;!𺤥6˯kو>TtA֒P~~ƈ#vc7OU'4\HA0j엦ySw?k.bL>= G<9u1i3vyy#Mrný7O>; Wy8G DB<-'ݱ|{z.uT?}P}=ib;;|Fy 2+p=_FAbߋGeXϾ FzG.߇CV7؝{wIxVlb?X/n'Ȅ=3N+ގ5x^g_&@mՑzv5||=p"/x_pϽG? Koln=}/LaGณĽ2xGWaw?.5w˴e#3 Bcm;~{BP ۖ-CdYnO7\c*{hxh\~< y˼ezыJWMvm @I`<.V{=58},i14GmFU(ݲ*jy8ߤ}F,YQ3nXCKڶ93vκSa0G:/ƴ9g㇞CT;lk!!w9: د ]m]۟ψ;hs1iϑEz>6m*\u_75'?z姇"e瘴?mO6cۭn :ǰ8hɗx@"6u -mEVy4OkB%q| {y N.?s]Y fsȞ'~ܠC\3ڗ<(jzGH{+J~6g.1VցmmL{:!i8?0 +#qsP!.+'MlY)|r&YHE iW"syϱ]Ćg4|sLq8`|3Zopi{#xs[f8[Gk֎=ew%$b;on3OtFfys;='ø|qcK/ غf/R`zGX4?n;<PyfL bt`նm+5 uAJ8d=|g"ƧpGD>;nP}2?{ "te/rkxqnOO.[g_DTa{7bco?sv`NG:ފ׼?%T8}JR bY"ϛ%ВР&ش%s]4aGDhqsws7q3TcNCK-ړP(~m{ ^D8>COXՂtcJp÷3yAvP!-ye5*DVr7lgX~眊>+#Е$_k\_PoO_)66ǐu_Ecf^pN>8݉'wzsێ|vxb. jy7߷,:!\# /iFRShYw7vΣQzgYE#qӱ[qcG غf/QhzG(ߏCk n k@<,#gStw@8`%Bx$u㐪0o¿0F߅_ܻ3\ycw[eU\pI'ף],ę+`s~l\u_6.l ;g'E{ʽwLgNfvǠ}Ջ~*CDpk$`bs|\K9?0_F :=W\{w_s3(;aGG;Ǜ{8e3~~\{&k<^</' qx'Ƅ@r9c1˃]Etbwa+v݁Wn+kǵ;QPi?]X|v{KBaeI \oɦ &*Q-2efN]+􈋌yK '_ yg e`lko߀o'?LNHW(^j =+n!J2K!\D__ ܚThs˛%[&s/DFƼh־$&XqSV凫l͇&(FmiP2 _ҪC8ѫ 6`F\h/aӦsu:1hE,Sǯ ʚQ#X.c/Չs8\.Ts +,d8(m[ɓ'wsL}l@/ďiZNڶ#?:7Aw5}e, n w~b2.صuK``AAAAMfl\v8H   _)F *\DXA$Y]BB_cZswYAAAAЕRGQޙC|?:AF?0'_\ ڲn]4hL*|?AAA?LFXxAaB_ϗ0ײ.Tݏ Occmwܳ   3Vr'H :A~9_i~gFdLB%Nɂ   C[3}*AO& \[d̺o-,ZL2YN~o*ͣimLl:>[(ec)]s)qgtn=Q8<8c$/$Ŭ]+ޣe"Gܓ™S9=ywe;n)$d+kOa-w]R@E؅1EJA, i u IDATTyaXw< ŔvɄ/t`f6FpLC\zӳ|3stbV#)A*jfeg3%j%9Et1SGV@[  n؄ Ÿ%t\"^Vu(^pHɃLF긖j5o5]gC2o:1d;qgi91W̲GZ6'gC/,YrztFoYf3Ǝce=B[zKP^ӂ 9֟)r#FQ`oظ5oHM/يv#yc#ɗנ  S}ͽR/p>ɃX;A r/N=2peWjEJǤ񘄟^lAt 6=$om>EӸe*"b缅h jmgk(Aťl\euNF=(:, ns/BK) TbP\9e~TXJVps͉sT 73#w;ג& Qss%Zzb%M^N[oSXfT*TLbPLw =Ȩ˃Qiw5V0!X } ʝu[Pov>4vǘ| vn(${&TKyŬ  Iٲ I-ʱ"!ɢ"k̬itэ~ QRbBru;q8 Zn;ߘPrTMڲNכq7/3:,;LgXGqPUl5 eSsSʚLZ΃Z6q 'A#9m؞EQIcy)Ϟq˪Edh*ܟ7J$,¢H.Ǜ[:s0nXYENUD?332ci\2Ԣj() Tju*Wj;Vzmo]Kjc5p,K Yu\LJ}ʈw[:wg` Z  |-o|M%"+E g]0nIpk[:AH$]u П;wQX1Ο;G}pQ&Nȝ/ph^*˖龁MrD64N&LJBw 밍&Us&|)|yW.k㵐'7 S_Gށ$U'!ʏkOsPͫYbדg%{NdF1N`\|:%ػ?, ytNX&&mnSQiB`I Y--k WQzD]\9• :E{ 6z'0 d6۴J؄5 ,뤹l^cX  _K*}Z-.C6+DN:FY(x Ƿ$EZ ɼ.NJ]YU6Y\wvPVlGt(-gb_[{ѹ2-Od`BB,OCoXf?_.I"QqPqToqHGܘeohZ02|2fJ i2l61㟃#=oQ8fjHv6{ {*3QX'4'g\"VtgE+3A ^qa1m|v_xA E bɂyy b{ݳ&~Lr]ԫ_[nҥs'-^B^=ݧ/`]WZeC.reʸXR!!PGv$-cX)Y6mO2S=$*g ޞMN waH˜q% )eJN, 9,rp6:7$WfcRtS \Z=&DiҲ)b`UסVrհBv1vgƊTߎL2{Jt١k~,xNjӫ>41ʱ]Jux+ߝ`8oT]6Цuz 'n)xǘנ(M(ߚNײά4ݷl$@2r{ֹ1R2OAAЁR D]ľTGyeL2 $[z8/EuvOO Y\?dLG&ثܚwViZߚi@E?x OZ5yϽҌǘj{ϩ ׳'y372Cַ CrS]5>=1.񆫻f0ܣ#{9wgѶIMe\Hb\e~-{]*?v:L}8}6ϔ.VsdO(,ݛ r`H> /ڽ+;\}x%mMPlߺ+gj1й*lϕ3, 7:>O:SiʵZ..'6|ނ 0`Рe&Xt)8Q857}xЀ\DD/DY0ыFȇ\¾;%JFihO94/n򦐥uط9yF/EMPuDeAqH#IUnNy{ܧ#w;L|eAMɺ .8Ʉ:I7S)Ei1be"x[;4 rdF=Qo$׾A\VQIZ][6 ] JD{^Yf]\X)~O56%:j{eעAAtT~asA.g``5 | x7Ȟ>7;B ~&; 5dk>̠-ȍG?0?:0mjփɫuf-ug<.NNsYހVGȐxwÁ{1 fԞ7o^S+@m"[dݖiLlF3ŗIoށ{xw~H%(b&CvNA٘p|9(/fFԦLt}tا "ݓuRy |RzD[[OByt.VUc,Z&10(̒*51Rc2&ftݞATj9';fqĝe2%4HpF}./nƦvL;5bkw-&RFˈdг'!J)ԨQ%0-֍V *Q+C6dU6r ]Ф j/qj]S\GWT,  (ՠډ?[BO}2:ސoC@ǞYX_~H˩!9Ӡ}@G=ԭhTm8;͓dI~Z5s"wU%k^^G:ؙ MaclAdؤ^1=rHTo 1D3)@jI%q0L) 97JLM)aon7mi:&lII} B*=Y`bb?kh7r/;| ^?7rsʵH|&*anpN>D"RGJ-lj93R.!g߶ǵCb2K,s2wY\*`q}qcQ!M|DD:"&WyQJұ>^={JdO586A*`g谆e36Yfpos"g-|6 MƩ45/}`I{VAk)oY{% Vu0ْ1e4͂>WX t}Vmy}m1@DY-S/@Ϩ{˔Y@b\J@=vUɌVt/K-S XFRM%Tϓ.TĽDhNV+JÃ<1I̲D-Yձ3zp25wbQm6_K[ցIrHӑ jR[pmL/5dHZS}d;5A<|mnS{}6/v!]wФ+#)G^7Zd,k֑sҤ8sufMm-> $;bAAAè@1]lx'C=q/6[3dczio#}D(NJ;;e4͜Ä;228U}.U acxE7x0¾T3nZՌ cLԛEiPJ~ȴ O:S}iM. @Sʤr=yܽq=x,P53p0-gA8/BI$߶/   KP%DHBGR)^'|Z&9 7$9}(bV^p.rLFEٱysF__bCȾ{Vey@%   q5Jč :`uNP Q u*ms"u-SQNQAAA@ c܍P;nD!u[O O    İ r6Z{KD M7  S:5)[iRVS_TiԪ9ѩ =Fm9cŗĩ,ъJN8;q-y{e'sƭT~pv*F= 7(sQ2yQlI{?rN/Gsr)/%3t?u TE#,ޞze řbvL]xMcsr+Dž1zo]{Y)qip<*ɉBU3:\i^'*8{ec1٨NNj^#lkWFHtxe /Yk?Q9;(sƬ~ M=>>T\nݻӭ{w[T\=>>_>AbP~7 mm'w)j!a\ ӰE_}q?~q8E-MczH1NA=8#kOq, ⟿s;k'F&=@Ͼ麐]92WP)c1sWfowMAf*a¢HzFy3U|,W` V'|ؐ;7r’~=i=DK3f~ZlT0b Hmi-QBܔj܏RZplF,$Y 6\\SBZ1.[7skT#{:ףՄ^9;bE (Kƌ\8bQ>F4[Տb#q##.]#[`g3̚zgQ$#o0fƖ& ^fUh$1/Am]p%CCCT*J BZB[Rq/~6)B2?P~b= =Mq}*Hp 拿Uvg;[W>r wB3 S$W7!\ 1~i\w&7MzyY\o(j 1(qL;J~V}0 >!"! ]TF7΅\i9zwþs"{Nz~|]*4a)lz+CA'' P^wԕ-n{Gcl+*v9Qm967\~']*fAG:F5B~'Ř=ٸ.e^G^-ďQ'+D9z,[7o'<,A.'Y'u?PeϲԜ|$9u(W&Wlw]^\x5t]֧ F~T?ŗnJ$HC\Zw<@K =WnsV组] 7bk#goȗuc򴢫ft vgis$cPdG6vg־ܼ{.cnXɵϋn: -K%zyz٦nȴ}y*Xۍ>[Տ3h7<ĵ;ܺv975WCx\-{Xv">oswk&Kg\=1 w'w ]z#QDnfOhv2_s S0r,ͻ6VI!׏fɀ%`V{ IDAT_߁BZXE= sVF3쨐˫(j/5~هf8^&(䄳Scw'~dzy۸%M2ZǬYw7paFnr2lTդId/=5C*/0L\+2ZUu?PY%i99z8ѺZg @ϖƒ1,h%xwdĄY`%N%89ϨOA>SkTAA<lwvRszR»l`z<35f]OS;0컝QsV;^#ڊj}k0"֙LiDBB/he&. "r/קpQfgԌ߈qWe6+I18b#Q2] ?VQC%0q5NI8I9YGeH,6̰`x пlsl"f͖EWk4k2SWzk뵏5 WǸF=ϐ)]+7:'Cƒ1f@(cRdf1 $Lk:3 ]LM!"rc5ښXpV͸F6cɪ&>[Z(i0kK H5`@LwX8s;W#PC8uNLf4_~$W_--88wB.|p;P|7Ȱq9Qre|4tQbgN唏m\Ӻ^O3lxsIvxԳyxwe_),9r[2R~i7Rkg Y^&, //G5*dlbd1GܞƱ>N揨G^GX:; Go*Kys4.%y/aX%^Lz؜} iOW`ₑ4/ F22BǙaΖ)^3*5_6NTT*U3~LLҙn#""ЉQ1y|&7׎f`&5qϨOA>[CAP_<\Zp{V+G8I:{Y0)eshXٍҹ-*y0nm¾7vJ%KU&~ (uGF@)U,5"ae~gNrDYVʂs8d>52JQ}-yA鉭q2A. ѡlRhi*mNZ.d}F۞iZ3aJDiaTupH}Edl"SAP15 QAu JUjytD}ѱz#H1JzYdB~[;(%Ldɻ0${naDgL[c;lAE0efkan ݞS(~FCxNb@lbZO43y+udޅUԣVzX[L9dp=#AN &+r~55]K,ʱ": kѫ֘ۛ eV,M˩W̚hܖ.)'_Edݗ O:.V pC,yVRS2am̢- lOfj58$۫Y5es7s}CFEmn^9%wh>ak9oGpKt%|;9ߍ$&d0$0Te)63"Ke{j ?I=ILrA;{=E,ɲFQO`]{mE"h+e$Wiviod*.:ι針a"DVHRXv#47ː;a?~d8% 3h)8=c:wkaf!ʉGt-c'78ƣdQ{k`M],aդc%ս<1s͟E.#Yd)5kՕ)S0ej*Zlj%3**+e|ƋUZ7OAt}(/܍~t_ͫhL,TFbEk7ט)N 1Ҵ qh)SiD͂}FM ޅ_ƹ OM}vx/ϻu38pm?3־dSi!\*PPE֋>܋8UJR d";L?QN*d{᳁$9( <71=ODJ=6Ԅ?LEcpzK!q{nurhYѷ&?lqr}5sJlbD?Ote#c~ 5Lzs}sx0pz{ s;4C&-D&t#NdK ^ɝ|ڕ ZI}T*iԨ3fDPP(P*UIuIA(T3O3=-Dh=%J_] h )gѥFe1[NȌCI:͓760iieˊcb4[ꆙKxG# Tlń]?֎WX?EeѩMmNN)n;U 9uG}>(Ǜ5 3[Vr őF}(Ѕѭ:c\֑Mc#.i[:%5y`VaC%["ӹQBm,;eZTIGb-Mx*GK<ܶΩ$z\0![4g҃\yHXeL_<9OFg9`;-ۑ[tչIY?ָ; ` g =?vya#@Zm_ۅHm%ψT"l>ӌk,s.t.En2&*>\U!S|"$ϦF}qÃ1lZf̚4:^Dˬ)T^Slp]B9X*# cM:022.@Ǟ0? Ja`V=jt:ӑ nuSφy6rmˎ&3cn>52y1aэIJ l2) :Pu@0 ddSfS72}~ZO1+IvYv,=0!_i7Z.ڍ{O3}J*q^)aXɃ P2g߀\Vx6sy0GTHp,M푛?njf1 r-«0%]n q5’qS=,svp"͆V>5fآ^,4&##s5Vi~TpAًYv)&i4Dz#C_vԳtfdY)tzĊ5i;!A 9PBmAcQzi)rӻ}̖-[t*MRpxxYƤ<%F0ѭ }M1GY"}AtmUt #d{ /(Г%Q0ʷNSW軋s g2#N[ܶ:& (7 [0tOkLO`5 N8rߑKQ:%CvNqeT89{+2cKLzMvKuW[Jz| x7Ȟu.ǖj2-[i=^>(hc]|uĄ\{Ф}kjώIs.˸-xi'~դALtJcgō/ $oߨ_<{R\ѣGnWS,Ū<,U7Q@;# !o3^?s13 Rq|@5ziɶ#(j͇F-Q`)GUur=>> f*I$llmhҸ1.^~KP4Qz"UeJ CVRҿdGl~M\`HU/\vsd垭\AMS"êicϬ ,/?$V`ŭmI8bVb^ڔ)*7rE:tj)$:īNuG/%gR/]s.ǦyV;_y::;^|%F&eOlj:o2tB:aFi4H~r'R-6_uܙ$e2}\gۦΝ},ri:cL+0taı"A4ƥLM.)3 |X…+*}qX}}}[H q}(iK{RU8AؘLm17z(&TP M+ʏ0U*_ȖL~ߎ)Q&oLĽZݎ /=V$d Z]>/K-S 2@J@=vUɌK*js=2+tKc/Τ4\Ήaѩ. j>(#`o_eD۹HlbV?[Cy+\l L?W7vuahj  ߄Ԕ *H>Ż"4HH96\k>jN;0^!X-vvk^%% y0~kF7.wj am[){|hgjk=tScKu?ǐRI[R߂3̊WvF1V$n@Ҥ?t)#   %Gr%$^IJN#؉Z.o9"٬r^^mN@v}:9L4q;zMcx 䶐Q̩T0O}'̘l-Sr9LBt;S &-]LQ=-"6Y$v,o݅ ]kxt06ܦ * 9v55k;`s=~&]-q.&ۍM-oCo:+va=;5˹#w~̈́ܘ,u_'s ىDLF7لE:AAAG{ҏV Xqg5ͥd75='l֧Ӑ45tY#ʐǗgļ濇A\QǪ[]_nXb4-AԘa>sili&P_< t|'6bW |g~i?v?ӀBZ>7$`?z{v`f@LR\!uAf:NھA{ڵoikkkKfl %Fv|#l2ʻZfN([VYL)^:{:r>`ʝKlYsKn5$o:d]͉T4[ѩ åGC` 8Ѽw:Q:WVeP%mM77oI͛KۘկU ;-;*.?E%\FVE{m{mX, ע( .>z3spggN`nZ1g}>%}IC8bU4wZ~P & ރS2RwchVlTnL8$MF.L\w7ɝk/[bFYS-jR.d(7 _ cO61%>[VԄ*&p00>FWtBeIq;U)Sbѝ"31NΩ17kLH퍍 NNN899ѿ666x{{gxI=@pa)ȋ qcDŽFHFɥ'fPF8AjgSHx M e<خ.~k3+:#[-TuwD[cu|_[&7$b}W:!J (?riJ{Gc{3{t:NGu 0sv]r jomWj՟EnϝlbGa8Bde ^J\_2;]ۢ ]./^)tv"N9~4ݹb~87>Xڌ";z5~cRI?BX5ܶgtX.HόE{ߛ.fR%vnו7c(["a}Z &y KP'kbi~+ۨ[k/*q`),`VX*L~Eު oYAԥ˶1OS_U508'q>(ޟ՗K=WI&[|JU$֌K9܈_KQҭ IDAT&O<( 2 R\YPp,?w' }ɑޅM.Z@8`e!g9BDKT45t ڏۆ(#'m UZ2vQJMݸFhbКq?LClqBc/?ruB`niM ;xQ_ 9 .RÏ։'߆pfͫԄrfO)Gq ԰ü_Rw"^:R 楒U655-L0/ ]fe(#c&UyoIhꠅ2!DKCb6VFIԽ]9)$˷Àt4SQD͝'S; )H (ս.¾~VoEU6Ttl'~3!)O/'I+#ȅK\_76P*4It{I_w$?a`rLQ'ʐJ2e^;wEtt4#JSl>Q_s_5θAۜ23Q7'Nxg ʸl7eprg>*~ysa:`ho_|j<3uBT+CRbnn=įhY`A<5yr`2!2^5?{y>O\0܎ʜDelUG]I utR]?+Jfނ".٩>< x]m/_̨\zGW2҂:1/_y(#<d_=w Bjݞ̀gƓs8<Π\*Kzd%wѦ/Uy>Cgf .\,%)(),s f0dpMPhi.ґFw%W^vUؗyNԏ=Qwk\B?ruziHY&MO4+M!Q*Lo zU -gg||4{YumEZ.;=݂CԧVey)f?XfYOqxtEi{6ӽ 6raZziqn[218V\Xo<3b^f'mH݋ $xf._#v``{;rp~Ob5M5Ԥњ\DNMBv>5'mdT֌)ŦyrxRM&x9 \o= Ԍdo NE),1(Mwa;IړdC?W%}dur^>pF2R?q )AERFj㢙؟+#ѯNf >L귰}_7ţitX?N5Rl ^uhkhfZU;i|B$̙39s&1ѱ6OϨY ]y5҇5`<K'2&}`SenV#&?ԜT kKz8 lj-QƁt.0ɈyʉFtlkNJm(4 'Ei$yĉ+[.<V2gg& R^t~oF5,FZ2Pswr3$& (ofwlfXXn17ٛS7Ľ,lSlZy+ҵ%)IЯbֿ҄>F axKny6.vOJtlr/Ѧ׉I >UA  z`:xo@'. 9aG]ȒQUwFI| TEjg ^NlI[}L;C h&a]B#H#Cw&NP}$8e|}wfǼxωt,]Y'ϜPgRJ`rRw|~f:^=D5i-K >d^BRhBgTm1Y弙7L`gщw6Q#2מ@ ȐCy*v @!Yg̴)[*xۛ'#Mt>:D+1$ ,1ލ,z> @˔vc9ƒм0K t1h|>NGh@uhdc++ jeBNMr`?=qNr%(Wҥ毶Ĺ$vmi${C[T (xG1i7]N `\@d4M%:zhk,LIU414j;g֕H_ZajF|n#Y G ۊL+b) e 3IY0xV E@c5w(A+_17ς5!.-w*ҽb4(p.֮c4J,Zܜ3F\{Oza0~ԟZ!7 ruWYWdĸ}r$u<(+a2;mprru6" :uQIhuKt=:V\{ Ұ!mM@ Yg]։ஷs)dh`EGg|L*@O?@wqA-! a' zpYR*U2 Alfxu`Z{p%/B#sU;ԕ3+%}p$;~KI]!{,Ew.\[BQąF> !,"#Fy5 !PtT+]2'AΐLmL{R019y$vTUW|"$2l,kLibӁP6G1yLcj'ˆ~fKm6PmG)Uo[)J'_nyb9K慨zfA)awx@4/^zxτEJHRV^Cfͱ 777J RJDkkQbƽϐրpg}@TY2s@-~nwQZ7(erWVo)}48'I]Ҵ|(οNvY!|ޏۆPjGdHFY`w2˶7b2!RF\W wHQ9Ɣ Lؙ:-YG/2LN`bۜ@M= r %/\::vQp#먡h뗥*OTKlTxWF2ԺXGo[מ@ H t7l*1AnGOFl?] (4lEd\IA:Fou&nNPiD 7OJJ%h餞sb޻FIFVfwRd5(b;p6+kSs 4e<8,uˉ}3uf̈́zs6iD W׍fJ_,> ކ{,9f1lG6 Mi|3%MFXnUGOy>ά&HRb ƞ98 @I(VGx3Ox#C㓞 {R!ibq*je]Ѭvm7/GIMnSTJd]ġ[*]D^d@7{M4ϯub93aM~4 R1#DL0&eM:Un_Pvj%=M5ɂ91ƭN3z@g<$3>ì. :ef Oa +R4Cu7l*`;⫚&'Eq9hk%WN>Z?iӨ'AHR*D+YZP 榦X5pd._M9~4n t,i>3F%8T⸵y^lZCH:U/_<U]S:2͘a[? 4[Pۮ?kv\KH-95\.t>h (g7Lh{˜`< ZOUytr 6tgEZҕC#C 1 7Y= 2OZ &'#QNkI>u(3`;z0ϵ# >@C:؍enSbbk'xvzƬAPD%U5gW/'iv;$:˓n^6:dƠz GD Z9`SҫhcҪ;Xu3s4Uh4RD]U)}X_qzO>)U-2ku>#^^^j#FFF%)SۄGߺ3u+M9B5HIM\ @ jU=Jm;X 0kI0Bxt~' u?32mΟNׄ퐂ED^fh6iݝYoc1 e:T/BcxS>՚˂IXh2ErY0LF={ͯ' kPYuAG%pxw6BqptYfV\@ ^Q ƾSUFbnxwίj:T7o޼Ig:u$ FFFt mmaz ?$>$^ gqxw1~m_'bӤYge:NSh>RUggSU8'$BVtj,XAѶ%{kx!mD,ģ!J)h KSƪ5^[7SLiR %TX\_+ W= +h4a$ \ːGuo-gf b}#&4r 65QF1zh˨Qptt:GF؇Z|4o:G$d$?.3 h^&5E zfih= -]hleX_xJHTxϡm[.!=ү]|PsKk:NH\@aajEL tJN\K̶:3Gq ԰ü% IωGxMpR駰fJ*SRS˪.0>@H ?M =[P5@}]fGE]tXF8@ |)Pse0;Xx܇8 %2m : ͕ N Q~huHMWx nx/xt$+W!k5Erڹp(6]~3.H3[4>,#ft>JG-g+D<'Wr#vsw$~eoO܈*]y5ZTS >\pqʌΥG{t-#-</~rx<ȃ@^L'"MsLZ shB N uZxQWZ5)Xޚ942ڤ(xw()oMiuQзv+ @ 'sO%P_b 9аK D=~S]Wc޸/to\4r'L qV_6ߔĞsjrͨr Z&n./uU)y1je-.~_3r~bS+S}\]Efo,܋\vM{RRC yU9aZziqn[2pqڹygL=ļN<ۚe\XsKיϋ?wkIf򨩋̼TtAtՍf5K7˘mS~Di κr\&C/'tbbcrp@ @ W4^L6,fu+UފtdIdt c !qPN8hkU.o@JuΣ[mg4Wj:4sSB4*Ny Mr`?=qNr%(Wҥf\Ȃy_)> 5U"qz3ZZF}x @tWdV_ӍKM{S^*t](ޤfN,;%3iA{,A2n  Hc@ ώD=V<8K"ϯawTɛv`WŽ'o].4;2?W( )7R(>8V&_1p?¥j]riSĨ:n]UWB#!T8YD1ȫ 4R;<.:@I̓ *10e4: KY!{,Ew.\[ G%q4oo\:"NӳfΨS1dPrA!ehWʖX+onenHyKK[Vaxx)[(e73q8+,VR,w`}'0ކMvט3y;EpFuLdXUwx|Nӌ t赞וw|}}&ML9Kjc>Öi:4f^X@ y| y*ĩY/_Ȇ iayqY<?')g6 ;vH `3yŷdS<F71t #U~ҹ{y4"F3N%JE=ԑKM'$f%4J4*2sm:(ok!&QY ۑMy:y}x%E q}I-( < {Num,u(E 6<zekE/ƗS+un]gDfmq>,5ަp}dϦLV.\}dZgoWu&뙖e<:wd`/>CY.piIu2L=DhذpMmubkwf&_J|A<4Hqҩ=]Ϩk'L-R#nmO(o˶ۉzK4cX24]v3>PsB @}W IDAT v~>:J-doj3f?Ϧ#ԶϚ0Ió]kf՜ӗxg13a繊]R}<Pn wdVS.J#ؾb;&(t0fGq%Vߤ& tMAuYp 4(Н]1kIW} - Z6o4,Կ<>s{^K*U.IVt|#fF? $&TiО ж)'pĵd_ %Q60kSn|Ua6 zZ_>5$'uCzj2BU ^YAhjY$_ZL͜y Twft2eD<`P8G}d?.f[(1ޘFdkA^IfEC3Vu{N@NJ|eI aWH0qMml»:P"炦Z($[6MU.'_iz6LsiѮ%P*CR "V:: jceBSvEbbW~}< f-%cUlU!c\bL|[%+sUG.40ғ{z2W2Wms40o¦.EPly ^c5t]Lǭa2::+ =wj_\Ν;SR|wI.]= 'aUQ91Z%>`z!orc+d攃NNP|v.,8tq! {6wdDaܩ<k<'-#Xk3/ P* Mpb>^FDXeMTzD]gTĺqUm4l!G"7bsU|(FXBpM{* @ s.nLX t4BWT@ ܌al߹Uruƍâ%K2#[ bj ii\\(mx*cnF>͕̍ ) dR ]92VM4n˒+j x!X)]SJ; bqi),J RY?6KډrDZ?3VyLEi)N$z)ȟ]"rz߷q5p#WZUưg59[âݻдz1>׮=شV+J'Cɢa:CkXΨ7RH @ x|NQԢHlH1G@N)[jE}2h7_gȑ 4͛p4(>Ћ?a)bط6OU R֝)Eh8Y5dU^Bũ1!X\ϻD<q]>uJSPW ݂g> HմHN*~)wpx m2kŽ9?@ EbW/s,u 7 wHaed]ycƎٳgуV[3|Ĉk:<5oK<|4uV&}-$<)ZTdQꁒD+-ڌh8a4P;կ K2蔥9| iTjhк35LAL U*卾LDF Ѡ,v-h95hQRSM Uڌeeţ%WRE0jd颿qDZM an𑀷R(=\>שS ZRA.PoNAuхF  g򫡡%Kذ~= @"lp$)F> ]8z/!(lMu\i6㴾#IJC$ivL@3_ܾʩ=ist6r>=S(ya  (V Jm.,_х$rYsy9s|x'ӥDM{loe3r =Mnj43ͽ~I!=a4 [ Zr]jI67>wZͪ;ij M31e 7ƪ~[Griw.e6Ą/U[&l톍>j4jߢ-6ђ\ @ uH20,eL&# 9]{k_Oboц4(b^9%td^/ڌ@(ST.߃ @ @x EkrჄSa:q&Ɋ@9R):E U+z-mmYa)]-1tZZZZ#T_e@ @ hhzIB1@ @ Y1t[sSͫ`͙ xd؎)eox3Y<8$8Œ~q\¬~-UsSS82}/,,h8׍SrP\]3.2 ~LkpZ@ @ M2;w#s9|ul>v:{S&.'py7WԞSPm1v1ʡ.XKca]? ūUՅo2sfEgd{9̎sz3|2fu?.hȝҋ:2#&+~6eBlZv M--uȣK֬Kiۡӿs0@ @ H h3*ֽJJ2 +dﵩzw$1hSqun˓X؅CrH Psf(HW\66,fbJ"c^6ocA zQg2 ~dTݷTrH!b@ @áDh!&Rj(HacKa<h?,LMҒ|RM* <6-Y׮ 镟NXɉk2RZ)q<ꎓmU,MM07m $.iHbdމGxMpR_!-]hle ~8Չ{KsZS,'+fdάD?29N+_@ώo2 i@ tHRy Jj ["`ddDND;@ 29uKC/H{ 1,uVH>7F'Xּ{w.fJxkÍ8ـӵԩ >\pq(?ү^oQ?;RIW5O\:rZ2Cr93#s 4N|Odlܫxj fi۝:LvQ~X>Ɓf&gJMpƎ)JJSq&țO(x}s Ftq/oEǜجп3E5ʚ`nQVr!ÃHWcgfؓ'|r-+bnjBV81p{^A3͉@sr kvIR l?<:,KEe*Y~ ُhtZi'(˧#u{GgY-y#߲& /c!iCXڌ"7o>ʉ{Y&D!fVfuII}me6,8汏o~ ~0gy`>Mm2d@SHy5*(Gkq]/90BM*;Bܓ ut1KVX,;v\C / ӚH4cX 6=q]K[?FMF,=(w:(נ7nc븞WS?Or fu0J|S+25zpdI]lCMD0)-迪>%k`4p-OH }P?/͗zR5JMl@ )׷DߚQ8{)G$һ&O<( 2 R\YPp,?wɥB` ?Eܬ%|Trbf7&(# {w# VRh'^GӨ@JR(xG1m-#ƾK<U5t1h|>NGh@uhdc++ > e @M:a6bS2?iA{,A2n UOѢU[h*g&ElkuiM-dv=7'߆pfͫԄrfOV%V?`ۘԴ0/tyq|ȎvX[`nV{_Z '::[' tD- r. ǺׯΤD4k/k_$LlK7o3h.AsNn$DJe:hLp9imH)AvWa8JHIn-rR-WM*? {7ɻ)}K_w$?a`r6FT*C*˔ixMTdT*MMdP[p RܜQ} }QlBO6j._"g棒|wٰC_hs'B.3"e @z'Y!{,Ew.p\Sj%mPdV"ꞛG%j?]nYEΛ D;(.SŜ LtM(C_~ WV>XUcD[CN4š[ ^ #OMN`Z<1dm5o ʉr+%^jLtOSz,D2a'-T;Y-8{W~{/KfNEsRYSse:HHR "naQߺǰ33^&=Uvt ^yzr+EO;s4dP`rD$B%14N%>0\gImqM1̵V`/Lϴ\;sh?Is_[6\:{ڔσI-zKKrNYE$oc(F:~1b‰Tζ|O"Y쪙"*sS3,+֤۱أzNύL)[ :ar &F_薦^ɗRah<:, l@TD;ElyNb^lEQ@ivA.,;gggΜ9=3N|{ii:ۏ%+3LY3s6N2S?Glx߀! Ͳ"o:Ph]]V\ø IcO6 pշ2i%8伒G@'|ZP niA.iGSgE~T̽hK]px_ƤAkj+!f>A\'ﻇIXOoaz?g.0[z0d)# }2ˡ 9|8}͏Hz n{ēa$a:23PoLRʄgtX<|%g6}F~F~>< h^Z>.B25f񧢤Ey~ IDATtmev] ɺIMiskX21 a#՛ev6t+&#Mi;ɊbݲN3>4eGOكe3 ;sp`abow%1C8X'bPHj>- 'W8m2rV-b̓?idP.&_x=,/ ihxGv"?N ``G+X s[4Ay/@e^|VxYѕOahϬU`6{WZNwnYɓ7^H^%]?Gu^S|(G˛Y6ѕɋRݵڏ{R 1D;粀A jqbJk[ƹU:%G ^'V2yy"t?|Ft],8ٞ cgBd@vy~ŨdfquA%SlɃwRQ1WI:[Ǹ(sEdd$11[N?G-j\ da&ӏbچd<;BJDkRI4-B%5)ֳD]I{e>Ll͢ qj4볍]J%Էƶ-y3s G.Zg*U\t uep]Dj}V4r*%;>R| $ԗ4]g6-dPshۦ6gd\WeЗY;I 9ڢVw%)tEǭ液WYv"F<4 -(S5cGK[Ä{@" LѾykcjRlL a#tίrQ5&Ɗci>1eZ)ވZ'wBY'%-r $K7%mSyj݆Cwd!|N{h4'g|)xTےV#xε_E㼚|:üo6'tUĠNPuEe@\i"gKC/;:p$r@ V40͑$׵H H`VӐ|zJIW@1j-YW'/FA(| 9 g^|Ss4nm~_C= OEsfK/$>Ӭ˜qb8cܜFx@(]GޕCΟ*#b?&TY T& U7NK7 6Ւ)29jɥD4Vlы=If2GZj/If"# _ZjȺNHNba!'[s%7H8 _|{.οrBn,`jL[ 2P, zX-6:TOw`Vt0H$A&Hw2@ C3h]=c&Y5n_bZ,\w-)v^7/_ & Jyn}rRO|Z4s=+9?LD=ĭ$1guw 1 dU195&=r$*fnCo1IkvճN"~>|M^Ү];ttO$o@AP8QFg-S~lYco$ TgOQߡϞJdۆuB[;mm .C@e#iyJrA!CݱSVؼFJ3>rf@fzCΝ376A~]~(j-IA(kkkoZ' du:EsgC9|4\x v  jZxK:AAA!+ jZ'Ϝ &dR)hQ A9"#~܇   ?Lt H5IV{m    LY' $  > mZ3 BZxΉk^%M5& su8]K#o/G=P LlA5=뢼fӸRX(4=*vLOAq< qԊZv6R^И /%KԻS,@b6+O 8.:H,$+yn|ft9ec_I5 ߨ8T.~yĜ|M!~U+mqTX>Z>b_9cylmlڅ#o{Tq3N||x]mkS{_Blձ-֚ j ݩ[.  8P_[.ySZKARP?!I?G!Q;^}W:&vg[3>>~}sdfC]R!t.فSw+ MHNY;U=yqY&'ƬCE 7Z#ؗA^Pᷔ%&3O8v]{[OJ|kTF Ѽ=46=do/On_=ΚqMh#SzRUsTeŝ<} nafhFi NzsO^;;s1wOn=!{e=”/y.ebTwP +O.,ZENO^-@<_wpdzC;f.Fi5\'leFJ>ר![+ĝ%W3eZ?i)̽XK|I0C9sb Іn=ԯ7CĈQ3A8M]6}29sJC"ǥ PXs ǮPY݉[yAć'é޶D:A u-2([Ξ rs?1*1h55NU S/;ƥ0iQgls/\B֣"g|scͭ^nX XiXz2pfOid͆a"Tn=cr 6hh-G~lkvaʒa>ϰ>Ȟ{VҷmL0FZݘ|0ZytH˦2J6jTGwpl7CFxm[0k;/QEGrԮ(gc] :ɣ$%26DqWvd܎G!Z7CUFR|O W1RM\q8 AsdFV5y|9гb+9fP:bç-]|%)h\&Nf׎=Ndd$QH$DL5  F#[$:CZjytsuT ;Nq)Zcի+ʉz{֙ll(ߠ7nnG73'W\}2|>ٰ=z{Χ>~ 9ߝf=~[JW~]"<^A]g'p0PRge;ʦ;?:)Ƕr/~Bq\DM7Bgl 乼ч>~7]v=ZODÁ-~ 9d\(iJ 쵊&s8rۋv1MMTjgoÇ\L: ]W( (yd.{0Icc!7x1nKwwC'2LNq2Izd뫔23s,#Csc>rj얷b[<|t%nНzb4Qx}sb]q뷛z{XwOƽ oyqc6La?mO+O-/]H3kBn 1'&~ד'G u %B2J(?4UzV1x_93rn7QVLa'IH/eְ[;eT#›7b !M{PZa8M;MYϭ);#,/9KnPpԷ~x=7mM1Gkz̚%n w[sP0iahеw@M6::(5jL h۶=ݏr NmCc" UqOxA|H)v\PFK /i11m|ln6ZJwS'-wͰ/7J-3pYaLY3 NEο<w|ZXɓ--x-E&_:sjjʕ+$:'ݴiӘ6mj$PeZM:$&)!ĺ (0f?-Ԩ!uI뇊Ԫ+sy?|趑}C' Uj˘JmcvD:w73b8'^)%gl½Fb4ޑ=(a88:+^g5 xhJIV$bݲN'6t 41oڛFt0"J±%6 ,i6xhliY/YoA~_qK*&_UŜ79ic'Uf)áNYzH0(ǙG1ɽ7{fs԰;Cs,lep?Bycuз 5u4/ ՕeEGmgv2c" XG\1I}s mȪ )uY%M$41Þsȭ/ͪHD *ekݤ='bO ?w!/OJ)$Lh𔇷ϱw`*aŭ/#c3_gd-"?|_ I|麅0]>óp,ɻ}SYӛi 0ZWƆJ.N:[l+7BC'GǸ0@ںHbb,~ES\ԝ)pDv6jTq)dCEj=ӦU$u4nG~'?}r% ! sZt+\aȃo|'\Ud yͫxp5U ~I>g@=s3B+'~ZjSLMMG|֦ E к{7[ĝ%sa+}poA~_g]ұ;'0a+/{=1w-Fn$,=A[q GV!65hbQ-6=BnaG*sF%EW<=4GbPS'yuaOr+Gc*ȅ~7 yͭ.\+Lf^B|0)@^܁ېI\IIIz>\zOQ{F4ɟGKi !q9%./(O9u/m6'AA0)aw>AkqU;vrG,7~Ǐ2ސzc8<:};ѷn߃-M^l>E" y rxb}hSX=]Y^E cW]FwK|/4=**౫Wr(KgcE]/_-CW9n# ȋKqzPvؔq^yk\_AQ\ѥh^]L0kaR'5,,tJBXYvT\@?òRDk_en>di:͟(|n1inm9a9ٰ> fٸI> I=[ޣ~I8ѩ2f9RiE妽YcB.!uDˌ|X<|aVё!~h?>ƄJziRq䶟6%-EAH&wњa9ok ,t6p i: 9,p=?הE3W-mɒiX5aU3ӷ VN4o/#(Gn+8Zڝu72vh|CePZCn銽!Ni]&NXن;ZVbp4#Y PcżTY壍y]շ2ZҙFye2{sIl (yFBUYqG'5=Q@RNU~V!җ:Ԅ vܩAA\(Y'LQT6Y30tq.iIS.u($+'A2:j5:;loYӲFCBh?l"e N"i9 BVb./c/3GۃY*6͗]^MQn:-vvy5]:ˊ(i[ iȖ_LiC8r5SoA~< _77/#& J~}qds/Аڹ0awO0ÐJY<A* ggȣN>L9ì7ݨ<;niu|ݦ\] LLMhӺ5::s A~Q?c?ZZҠd7m:q ĵRJ"p);ĶW]<}]*֨kB[;S2m .VgE YfnjB&ZCnɓ&'}06-ɸ=VGG:dnl DPAVc ρgvi<AAwM0!    Y\I=봴&?,g@6=1]   H#thOF=AAAA!u    EэuQgPL>NU7[~4Y!UT`MZח~uJB+c~9!W2׷?6w6<481BoЫa]q5]Z]˽+]y(QOW2dWOǂ:0.Jvڪ 'm"`./"[3`ƪHZƒ)^$   ۳NwAi]2n>PN K Wx}MEFJ hX kK 4aVOt BŇFgvʌd_nٹ#JW_,>0ԍbzmT *҆Z'Qj̈:JUT71Ʒz1 )U]ߤL:##:[6V6i;v;!cZ] Uk߲8yW6\v@4 +7G5}) X8I5"=~L6rPY!X ObC"3;O!iVrAA~_9ԏ=h^Jx5{_bCd|prm(Vr#},kExx齃ΤWh0?a6lD/htg!)ݕ}Z-Ice׀~xNlPvvXD8nvaW<:Â_Y޹7^FbA>ZO C`eX(!'Zo#eŭ \'@^Uđ R֒.s͚VH]EL2Q-2cꗏQC]j-N*t6ÎxQ*A\=sxz`UtP~/{1t[$'G|gFJD>ʆ zQ |B:/0PZ2Cg֍e8[~8~τmlP'_ =lazNJ0AМ g͚s૬{<y+Aqnċ qP P?훆ӸD5S?A%Lf3Z>0hT#OxɆI۷xrhsɋlAA!PLaR$菱v'%+*0Q"ΤNQ\U6Y2:XG*z#AʻXdF A_)e5+%'~ZjY³S?g69_J5s.Q@ےV#xε_E㼚|:üo6'tUĠ[k4Su~Q(h7½;8AS0$m[HӐ|q;N75Yc:0k#j;:WNuǽQ@%1~6LՆQ_x̓.qbiG˿+Zan }##-@qD[Yw)u6LOA d3F WYIsMj A(AȭN͒ TM͋kJ]Sc?4ѫ!ph+ eK{Eb!٫z^abi.^tU*eyLw$  d D;l{ډ%UheOrAN,.oH=ArN.X=ʀzQ'VA_zl]c[~1J$ (G4~Ȩ~Qs({׻rASed_I*Af,mO>Xu*8" qD"/gYA7)LIcX8*_fKxw /*v/uG5Pz(*Vb2@!¼ܹ)-h䦎 Wfܖ 8_)Bth0sa$a<9w@| G)%JΌnn0tPi />#߯DdD~|ƙM;y_E'A!/e|roeN\L.uVe)GP|KТ7ڋH$!>\]3 w쾕F.'& Yr'HysssL䲺qt(n&'179 ǵI͟N-?~tUE."q{$Qy_a˸ެ5uf\:%!zά&C$D|zō8 @=cWz+G뱴UKF,;FːE&6xr_]FYRtOsB?q=ԋUGH֥R)ޯ_ӽO?n_xR/% \tr_C@ʪ%* IDATO7y@\AAAic7BGmN'[ƾg=a_:: ="RG>Myg,nXTKqHM) [3f=C@fyyymPY󑣽!w1Ȏb!sH$.=E}f\<}]*֨kB[;W[GEuJrm$ 1   T0ѱMB;JϿ2qďQ7x c`o uIӼUf!іi   jp5n)Lt(}룦:&.4r^ ,w3UWeЗY;I 9ڢ_;Z9}s1=zbSa!4CkAcR!VAF:u3͜>- ]w}GgڤI3kf,^#{^!G8&=zuVV\٫w}٩suV̛2lx&foL>5|-.u˫txjL{ZV%ɊUIm3qE̓<69}XKRn[9vL13S&N_9-{yFf¥wǟ#䡉˓ӎq[;9~"o3L[9&[G 5}ݚ1kYq\ei64u5?y˒d e?,Mb]R1u@ج2iʬ]YؼN]zjژf&99j6wsbK}B;<`ryInܬhj 3/w/nݑm@9'umy}?}Ǘcѭ}Vsk'I?'T3sh_\樜>|ky{؋3'A>uZyѱk`;<+rT)Ytsv9؁Wզr%G攃sBukj\u{oSpo1~ht߰}_|\ëyjfcu'ԴMMMi1#g}z~{cNާSuKK5-ɜUc˓ mo<5I2dܹ`kgԩoJ3ܹ͞K<c3dnᵶ6 JQV۝YYbBBuP !@!:(X@2WM\vk /ܾ%"ݙOь_Z깵S#'pl[ߒq舷'O5>a9䓗(fgOsޘ^ι(߿kVVM3K./㗷>oӜk7]%ٷ?1 Af|-^YZboϜѯ};0>rT? ̹MV5u|n͘a[utpk3WOޔ+X w?5W~*36|=،}|f__5.=qiy`JLKrR-o>^ ~[.6~5/Oߓ 1?쿧zuZܝ/<|ŹiyrѰ?sb&,&T<|ؓOч|ytl.>ӹ{՟-muzG::&`/2cwv+2G򽏿.=Z{vٶ_DUCk];rN[wMmFrUsה}sߚ&myl=rmt~^N[fצG/ijJdDfnM5"qN>-U|7[^f3px @}rGdMd~s,˃Ir?:>F9syﺶ6_X@Tn9Sor__1. ek[ax}g6nmPr|^79ϹoΆezMw2瘽_p:gЩoJƧquUsĞ5rͮ5Ms̡{wB=o|o_Ή[/KV# _ ꩺ!#S߫;7WنԊrk!#r_ˍSm:kդ͏W3螤&OHNx;eݹſ=󚒤&ޕe]#V^3䖫Gv$I.J^gg.&8= 9 }7-sW/] Vd,\Uݶ~j"+dyK\ V퀗j2䒟.Om]8fN.{ǻsٟW vzI}9/} '1U+W|xL^ec]߼sM~͛ack_mzg5ޙi+:Pj[iW]g6aj]Ә"l4L;_]:ìI{SsNl e/fmf,vq앤fhN>li.078xN|.y3k259my.OO}9$_nz@c3ve93y`|`KV5>}{%44/߽/;܁[Kf:) @dzeC>wg}MA瑗VIyș9rs~zo3hC2(' {6\36}kq-R5+&ל|#GYnڲ4~<7yScc6]?1^ѽ o'hBmea&'dNɺ4bj;ak͜ۮN+t#p~E|n>R +/ obmiW;(6W74l|!/ԿmEΑ$ie?ܒ3]~_|zm\~|7oCf>2kW>]9礼kfɶlۅ3Ӓ_p\_J0梜8oޞiϮgf7?ύqaOʿ˷rf>дY9kg7?&S^mumarٟ7ܟeMڬx~y/-k:8Np})/SѣzMfv~6y6!ӜtxM?PGeHҜu l[]ύj߁eIg_ǝm~??̍~syg'gњ$uȫ9-_rBl]8s=q'Ees/yYrٹ6]Tzc?7'nk;%9[;x][ztܩ/OO⦤ۀ/˙txmmE)jfϘ^8=MXaSSSgpx߄45ƜO _tV%*yly2~as^3&I5:;'쨵kgԩoJ3ܹ͞K<c3dn^kkk3pP`bBv`477u&Og,ؽռ7v'u3͜>- ]w;'K/ؽ2lxlL)= j:%OϚW6|1y,Y$m8rkFi&eάYxQ{Az1kϳaXng9{ 9ٷ_?`m+@;V{س]eKJ!@!:(Xb*@1:(XbRm $V@1:(X-&V@!:(XbfZYbBBuP !@!:(XbBBuP !@!:(XTSmuP  !@!:(XXڝuP !@!:(XbBBuP !@!*@5U+bBBuP !@!:(XbBBuP !@!:(Xj=ѪaeBBuP !@!:(XbBBuP !@!:(Xbj{TSJ!@!:(XbBBuP !@!:(Dj{TbuP !@!:(XbBBuPPbBBuP QIG!@!:(Xbb}uP  !@!:(X,jeCBuP !@!* e !@!:(X,jeBBuP !@!:hou !@!:(XbBBuP !@!*Ig=^jeCBuP !@!:(XbBBuPPbBBuP QV{ !@!:(X`V[Yre{m\281]{lQ^=la44tmQ^V[Y0o^}xYu6l::`m ttV[ :vm6l::+ح,_6XbBTbCBuP !@bBBuP !@!*j{$V@1:(XbBBuP !@!:(X$b?IDATpuP !@!:(XժXbBBuP !@!:(XbB"T:(XbBBTHbuP !@!:(D%q;X(uP !@!:(XU!@!:(XbXbBBuPJ=XYbBBuPJ`V@!:(XbBBuP !@!:(X$I=]:(XbBBuP !@!:(XbXbBBTRm:(XbBBTn E !@!:(XbBBuP !@!:(X%V:(XbBBuP PXbBBuPJuP  !@!:(XbBBuP !@!*HbeCBuP !@!:(XbO{u}W/̕}bXgddՋ{]ZXw]CdIr@}[LBNiQvuݛVĹ2!_׻-fzQ^tJ7hQv6̺M+rTr7X(D v{#9+bܜT_J{fno];t <5'JV&iiiImZMkRk}瞜rI澌#@}ro~TZZMZ-K]^gW}Fut7WGGGGGGGGGGGGGGGGGGǗ8Λ77>p׬aWTsީmin~]~g3˲w!{n٧>ٯ_տ뚛SRzEJMw>}zgժU/Ǽaե[׆tRJTT[ZR[m[*gR}[IMMnS-Ֆ677IT9l^~ -JSSs*uNJmm*>ȓNm:mThѢ^"y|R*- v g]ӦeytiROI6[SMG{걪g3 fY9֧ZZqL:Y%Pnc\jZXSߘ[Jb`Uf<ƄPNjcf2 j9"B(7˔;xRy$GY)&YYiy4,Mrba~uΫN703=B+ͭ]%k\?8RFW Eӏ_dž!6-N [$Ѭ8aTNce#1m=JGFu7R+oLR oabԦX6Ӂ3uNB  DuS DF1`y2UqDbè1$Da2*Y%0j1Ů;Qmk'Id=qߚv{Hv $\i.ґ$ ׋NFFnCzz:iiiv$4&:*r,03XiN+0ciF5`!c,&QDa&g8̀3V f6Vί;6ɰzǂ=?cWC]];+\  Dƨ&(gRD1\ j_QƂXΊ_DeZy $Ix0wJY&)~h(.lC4+W'mf=Mrzl::1T&08(MT9 @5, 0`jo1qBF }@RJQW11Թ5pK Toҟ|>qͨebCY%]JfmDcJ[ @Hت*EHRтJje8486+86h}2x<<ĩB$Tlay}l rsIW&2rN̽fuT2#O+6f1! 豳B9r,~fp7LVBMbkda471]3.k+_z*vM=nk0YQWE=%i׮]LVٴi# Y8۷o[{h뭌&XqJp۠!fPmG6tԎ$QVQ[RY]MYy9 X/xզz@¢BZ!]]F_jl6hdS=J\7qjŚ@RL2Kڠ=Am&NVO=U,cB,BǬ6;} ^h Uy4KDϾP?m6W MavJ ޑPhG^(_.W\_U+ThhUSDlʖ0U,-ױRS)_ʸLD"F9NNBà}|ؿ?*'n;}jVOaZdNjFW[DSX&q˴tҎ +~N֧!⏝9Pj22W.m[vVN=pL5ʨU6wu>U]U$++ W],Gş G]b5Aʀc0*Rqd6x"9)̴zBz}:us. V^\juB5mv6b)zcY9e?s*G)gb,/)gbq,( (X%zT-z_tJyglΩ-{RUrmp'H>v.|]c@9 04ΩH^Wܼcyʾ]#;]Β}˘p.I=Oi,::4VmfG~.g{aO:ڞ3/d- /Q݇蔱?-vƽ 6?-v>>+BهmOcyCT..O_I/qː]|1^nmh*#օm}|:&ʾbt,) ACW\m$C۝sߝҽMkN;gsM>cϗ߿/č32̑| E LAy(SmжPx+жto%p㓗eE@}qg/zwz.lLN̿[}WR^Ѯ-ϽH5e<9l:kGxnAIv8}O@کX37򏞧ҡ8c'O;3/;<1{/:NFaPY2&O:A;zn"vH/ T˯W>eCG*jh-d =v I۬聨o<FDi$R TVkv͟ϓ?J޽5Rv4IVj uTUVyGx*KKa3beK1)q睼,\ .2k؀s| :?̡yqi۳>g1Yd 37mcWa9mG M7Nv9ujӬicڴ:aBqy5^ E'D@ϧRLFGHcWj6(R7Xi,5L(̲u3Z`,4J4\=*'s2g/@^φFh:gBI-:ygLNӓoٜSK:#pp㚭#8l2׾/b7賉[>I6 6{V*-|YWZOfcyv s~mXԫlRx,߼7fG5 z'5cPZH,I}HBaťCwl}{l:1m`]7 ر! ޮƺdz^>O;}~،/|;sgnxiڞ;xcVB?uvA [\f۰xkOmOl0ۨb T䇏s9ڐawF,O^ZK{ogJ6:ڵogh|u(oj./O!Yy#ngo},zv }k?疼ul|j KgƊٲ=de>qBI Xt)as`w5~y +L{_>v2W@d;/ }n=CËװ* BSjv怩rDgx@"Iz6}:wiU erj7?<:u*;*mI_V\ԩSyf F^O?+n=KHs98p6m9Czu).=2z>D)?Q >3X޽?7('^Y; [?p͝8ۨfx|>?~O2r8y/Z|.,?ӥ~Rxp7)2)* XtZ;+#,Eu1Ĉ\s,m[hosmO}aPO Z_.LN[He| .;Jo:2ndgkq,P\q'# *˨ 4F&I ~S.d[wowUmDZ yEѺ"}D> ̄q=s@ϣuS3'y.J||{(۝Jksoy=9uU1BSN)W,ՉoLl#P-?;y 8}մܾmUDρz0Fl6M/<5a'ȦQo1|Bw bV:Y8L9htZnرg8Esbe>%>u:G{E}/O+c;m2voBM vf hXWw1|OaG H.O_Ս\5+ȯ6:Ͽ+j+ Z2fdgrzx:dAgu>d^>+FZyn<Č/7ʃݿn_R"}Oͷ|߿:'U@8sbV>.<_c}LjBV:OIy# ~LF8fބ;ۋә⯜6q(M1ʐFGJ;QO/<߽c]T Befb3<_?@zg"g<{M47^MQY!k~#m9s,]Yϵ"Vz{FkB~xgVf[ﭡ ]+n;Y1/3!9 ʢk詑0=mC GmLŬ qdUddkJtO77 J=6ogҲƍӰ<0mVqС%ٹ>AJWhok|_cm۶`luc\ݰa4m>EiiTUWA3tG&еC{2]ddNfzYc/4p[>YvYR22d"~}&ϣT;UN(5}u"YAtt3kU;JwyN9c uI4Gz㌫|Fwz˚2iu;%g Zr˔!ٴَ+K,б~bpsh:ڭs{ UrchdәގqŶrWzeWZeP%ur`e2eFJ˞H:dpJv,)Xwt uC3/zں{c?vm_9=qS42ֳ zW*!Bީ\У/|ޒ[F#`yx"//5(  &I<{-|KNk8t {3=ڌb$|,~]yOW<%|3T.ٔ!/Sm[YS|V~\:QGGUf+:Lgÿl-S'B|&Wʙ@QtވSn/3T%Yq7>jCP2 ?9f43Vʹ+zE5FυЛL9@e+NrlVե?&km nLGcyKge:-x-aZ*YEa'~}Ҳ2YAKf iɭWϔv펋_p.zݽ Ou_rI\ڶJQ|`o_SӺi[N=ߴ8 ϙ IDATelZ}߳=Bv/${v~/dgeѾ};|/~nw3-_6lбc PMe^fs5ƚ_11u5~'i;Re*a˅6~dž=-4\[R o@@*=]F-yss$*x+"v:ފJ†##46TWnH8LkwnCH>n7~)h* ͙AF ;ϏiW]Mfwp =Å^9ptA%u#E7.\N'V )@ -݅ =">` DX/4)w{ʗ3n[^.rBy} ?q; xiG@q /Tn йt +P*q)v'N '\6as wȵM`h/d'sK9pi&Ѿlh\>.<|@M vÎ ?N2Uo}SU/|ߞFFV. !ye6҂׊@y( Y2>^j;b"*=V7SiXb(U`V JC%PyKسg׭';3v&a/;u並p8q7={w_4%4Kt$+ڎ/09uꊤ'֊͈&T<<}2W?[RvdQNMe ˖-E4jOz]cգ]4n:gH!g@7-jD,e\:ʒ@i' BH7 [El僜 #J'Ş^j%4A(ƗH :`*UTzBg1.pH&#|TQT*3NqWUJ>ܕzlS]D/nxvJ?2C}x:@h~J=xFW@ypk1'|h$<(N~nj}!xgٔ)@@HB"]0iL`ApBO؅L/z^Ga-ɇTVࣺB{D s0w)z /Ue#N[/rUzC!g`фhFj!0S4A [94n}?e+hޢUInzo*M7QA@=˖܈ק]v9H2֑6]i&H택9d.߶s;3IK3e[M/>LYmbN,Y#1m bC!ZO!Tc q~q d޽/,\*k&qckڔt:^"wEEEE9N5K$cJ;2Fé`Mk^ƍh(/P'!>*j|~?.Lp\qL&X҄tAlk`[ң6 *mʂVX;fRif&#U$5MxFZXѷ1I@ BhRgPkx#RIۍrAvvvUz&vx4IJv0I b4n#V펡RMSEUSiTB.=QH{![`V_]G>Cz*hX*+ѷ)Anϕ&d]Kϗ;j\t4hN&}#@5!hҗ-7ZTAUV lRb ~SǤCH>mE`#cƑ b7r*29`5Ta˦qv%KV)uxQy $t[QjVIe$+s*+3׉V+hl F>DL4vwU_! eL"u(MO1EWPDtU5EAYiӱU$[Tz,dpuēIxⲏ A+kv2k1[M52Uؚ(]Mba72&t{B:a4"C|ġ4ܚ+QYn5M*la!w̵Dž éM[XN4*$UlXbAĆըIOш5bh3GHbH4>؇8@5i6*+4kCko4NmcZZt;*D;U(JX:2gk:JZl`r44[YMLAE򪩪(^8u?ҹ38jR!UB9L`5b@FjQhSʧVX=bu <:5L=x|L|c Q)6MMS TRS:ZqT9 ŮqQ[E?dn}uil|cN Z*!۔@T5 MFMDODFɂj3UNscéE {?nEWo( cӭE5 V[ixB}#ڬ͂A8EF /\h3sT*VnR4Ӎ NsEc}LST%kcTh =)` {h36HZ144^ a5$L!lBȚ5FES $RbQK+ h_ƅeNwhbg@6uY[mLNS렚 x35ç£@2FgI 3Do@5? bFV&Pj= NPB+U*5ɇ6WC]jLdG*땤Z.ҷ<Щ):G Ig:AlMJ]r֜c!Ɇ?$ӑ[ƩHNV D β] HsFjsjVZU7H]~ňF^wԆcqL}LlW%4kjTf$ZkjVF$SHN 4h8I@zv>.Pd& 2V}-ðH]qA[?*MG .-Z 6Y'k>iO(9!xjNձB rI8 m mnqi-"* T) ډtV VæV@`k>E p,"HEE1_mBf [%j&%Y2Kr8M-f TQTkj̖\lz CcPU:(`5Pk"jkЪ[BkT&ҸMsda3fibTg⚩وB |e@MJe"[}0A&LRlD4>c7~Xx np2Įknz#*b::/"m[(A]tD4~_L=53Q[Y|UAױR *eۆmg]3Ϥgcf^ҋguEkw*EmyUw2W/zVS5zOtbųb;-!ٷ{/Lq2u7iLuI㷚=NMSt'[MsK'@-^dr;Dh4t zsE]|7~C]?ˮ.W;סs1٘yFNibzT*t-U5νAx?)" p. Eg2w'՜T㵔I:/?o ͝rٹ59|}50蘸Y(i9j:W_v ~[W @ա}ly1Ɨx+,zGRB;)jD6}#դ~ O<@|ZMl:^2}녓9-KGj,2ӨW٢q_#he:ӍC̨FYuMS- w7Mx^T ݋$DOhT|<ϜIil6>-n_\Yh=ztG-8B2?p$0IlHVt? uucΣGdSI|6 W" DR_hC& )ce5M75όݓЙ TEPԠ\70{Xڝ]-qx|Bmxٻ-)kƏ3d> iBFVɗ}Esѻ*o|\4z~ߔo.![:7w\ӏ&m,zůr8e迸s9>=,d{L{^u[3ptAkLVⷅܻ|a?g}؏1z/߸ҷ/7ۉ);`ų>au~MZvgI9)iq9o`WȤɩ|X.s^}reBJߦ-/'m ̣Cuj=Ux=S><7 }:=7:0aRG6, J kӢLY ՐSmL(_{6ϱ3qı h:j6v^zljӨeGHKdT 7 ut`+4w)wW"lϢiow?>~3Ev9+'^mo|c/*aEɝmv4<_3L(z׀gM:J^/j&)n^$yDV*3/msP~!@ Y<^֏=70s|:.o}ۆ/\ F>G_|=1߻w7LB;f}_kʌ'_)`3Mp)˗=C5O30 |1K>͓I<,x,p-+t {nk9qjBK+y(Uէ)6r]syCpט 94| vI:@,h Fi=qVCI^a_] ǂ ЎJYGE%Nrƴ4mq"m/hv XP"T.ɣ_z8AhGv &Yg#Y/,bCx9DmK* e󫈱-ef'ePٲ;1j@߼TlWQ 5N_iVh:K5ه_-`+P^Dlf"|>V-d3`0뫁&\vkYtG:9-q]ZCam08V~#-M˾9)Cuh3N(Kip|m%g˼GetgWQ|OWYu 1U|׷_dߦPǨ3[>._BtH;mٗw۩Ul+㘣fc.jG#}nu8ЉZ!@`>\5 8^\8%T$ BěEZNd䨝G-uhTGQE7,gv.YRFԑ$"riTˡs#SLeh&WQM7Y:Ǚ݀LO):ayM2x4];~;jWۮc{<<,gDENWsn}>w9fpSŠ=4_p)mP|.Kv>dАA}ؑKq,(['N};!s]Fu嶦!@e1T)+Mr&m}מZmBQ3*I!UpBEwpԣE}?autjF晑#/)Nr]! ^ܓBtJNkI \: N@E1#5YtSM::I6)?ͯԷtg2 ݌ݟ|HQϛ8=X=:inVu:%iGi`L*RET5^ eQ~[n~~jApeVŁr?V^T@!mnQeQ7*?BʃT%"o"%Yr}֫(2=l6aj 3cqVcD9@2p[mO|&F/WubOez&[v' _< _lQUJ rEn-e|8OJnZ˚OY".e9 ^;YfZo]WZ_Vd7 .t%א)wK(BWo]>~Î~ k:hZ>=V+[v/-[}h}Kv8k[lJMNdHHU% [x9Lx+rR̚?Nv0XMyռK[@񲮙EۋŇl\uhܦ }/ȽO\G_lb^|6{WlӋ m;q{V^;'ir^hu \܄ອVV._|hw@:UP3û|lK?gm#}OL`²ø^/g92;愌h _3#cg_a6ݿCP<m"ǪSd-۽67P:̞y~LNb,\Lw7/.\ʨK|JD>+`st ,xN Hy߱܏}o>K{6 yW#}?1FzFwj^5њt&;uŞꓨFgho^CMGB3Y7wԧ畧ᵬPJ0Ž|Le sA4;Gƚo2oɜQis0z//8'syԣ]Wɂmyt mu>"L2na9mɥ_?GӌRmR3Fլ\iyL{a/=yW}5'?dŧH)=3E}e5q"]?> (30qb7j!v>~Z8C:.WO覽jV9kҟ0hMX;ę* Nn7-o[sN iFw|ZvGL[1lO(iT2Z]eK0}T)gsرϔ*Jkq Sf0x9(nT-d8Lknv,{V.8&-qm0{P8h:adZ[8}D,x582k94;m #ʐtXp =ᅲ28ʶSdϽy=TJ'uZt ح;d2;0]̝]WU]1ƚ1FwwFQ0PIE1~& H"%XtKttKnuwqn}:nx=s*aFPF|E'|ă{C?7OH,Eņg>ؼTdif0!@]bUعy e^ZV|8߇ڿ='طYuӮS CkL[`˾S6M!KJFhێN@ )=7  `tr ~iZNK,?'Щ, ir_|?Ga৑Ƨx&"#>FgC&yo^׷ _2HE& 9:%/y3oӔnc3Ӄ̺(s\fUU R?T*pLfǘ3] jzd? =ۉڦwI1٠e-lI(ְ?hYX,ҸLuȯTQJH l0,%0_3\> ;QOm9l/FGzFYE5U?nxv~m}9ɪ* 5i` ?IcT[Ꜿ+ V|gcފn?S7W~Bd4F(h:4YA4G 2d/]??4/r³2d/?1* 2d/xVhe"Ւ¦dh^!Smu{ !Bݢ&!B~'=B!f1*B!lF-B!HϨB!i !BƨB!i !BƨB!y7BУG8.YЭ3o;~MtLoZЁ}_ w0 i$^kB!G"8V啾0m7NL"GS^uU`Vo4a򂞻;& )ߝ-fu^#c*?1NgkM!DHcT ~FGbEcS9S67|}bϳzcmN!U.%\_Ѽ0e2#5BQWt8FoO|ֻ2&ȇךBl)pA"^fGHfM#o"0 ^o3ibH4Ar)(1d <_/W{9!M,qe1j"r{vci} >t $ 36d26ߖˬ<:й?Pbqv $ çoV[.+ˢ9F|-~l$xcɰ H2iiѻ~k*nDY%c '3DOն0{Oܬ5VgT?7~=– V-f\ϚE{{\vb-6.:Ұ7VO>0Eܮ6Vld˦4vȅ1s,flX61uc^kDc׬ch/~BHcT ;)#{hU"w`*]PU1tSD$7 wص5- ^IC Stio.kY:_Ie"һvaTƇ0E_WZQVk^f<81]<p*ZjMoQuCUGzVwɿ V4øB ,"+-RDgV=FVwյ+K2g4Kr3dŵܐ/NgDjx;pJ!TZ4zbL1lO1lQT2^ouYu G1=.7OxP³l,!t‹ &g|eZ<0ͼ5$LX^/D~'qJX~oU\3}) e>ǩJgve O⇽Ƨ)GYr)t HƥQ{m',+k^I/u}sa4yxj֦xht^Tt ĵύƎM\"Yr 1ڲJy_gjξ<*ߘYKkM1oTcO7"YϏBHj*Ȓ WXEެncH^v+]%o@H}Cw_DL~lkMz ;xWc]5~ѳQ̞b=h}]N>W,}X\޽q?ޣaѤʫ8mrhL6 !h :k(YXڲJgѕ}(Y3>"ZoĠٰJy8RTn8ͽha +pQеf_n\ G}S&3|Y|m|~,ҢȜQTTuJ?b+<^P&-_{bGt0*+(ٮ36'3ww!:dtRjŜR|cSpݡL &D`BK:Ȁ. %h _`θA|h@U8; a ߾6>*=)?`ٲ5L\:1F@WI*hĠ d&k& W ]4_"hw6KJYHG&һ4zCn;}3ZcԻ>,x{]Y)G!*"b0XR !8aN!Ko޼)R"D勂#&: }0%8X"ݺ}EQRe4g> a3F q=:9az!1w"¹~a2d/-@Dx׮\&:"O['I"&!!{=:`DNHd^!B،Z!BPgT!BT!B䶧 !BG;L&F#zy]'vh4j%B79hv(]GG Zlzl!BB5 Pޟ8"r T!Ng *6w0 ! JGSJu㈋˩`xjq*(Ʌsgp; !95<3/a2-6KF=H&c=1bEJVI޼Ъ,nӬOiOʍ2y]^ɖy)BV!q>TiJUvTON+Ԍi402(ܒ"R_Fd/dBq號P >!7Œ_06bwdz4<*7~>^hղW6&c )V}3.-[Ѫ}/f=M#O:X[y_hU>J|[{]dWӭm+Z{3vr+N}u_I6hժC&mfyxzB,㗻кe+Zwy\4+lyTO;Ǧ KFϑe{t` &LFcɄbJ^wl;a۷r-6ry'?~D] 3/U΅R[B*Pe3lLDƨ7h3z"4*C¸zpSI|FsA$\[ߠXu< E̚ӓ4o-Fs$աT!=d굜o>:N@ƿ3dJl?揮sЕLqgw&vNUw&̪DԱə<.2(B5}-F1wD9OqgGz_պØ3<'|{y~|,v@ajªf6f|=E|:ϵ 1,Ӱ#Y}n vzVȞ5èIZbέfS'&ӥmeЫMwIXD]E+pq?~aS܊2u+Cо.{|zgxBipwL浯_2ۗ`뱋܍2xMN]XR@A|׳UƃOUZuG&qP{p=krUtoܕA/*ün O'3i eعPz{^twjĸ_fNx9]u|c3ɇʝ/5O8eJu Q@ae>SǕQo#y+"Z֧6D:FV@%<8ԧxC 69o)Nk|*g~M)5KcfP6A[q0SRxlG1qbIvo:S[Qz,KS{Po\4I9h6;Ƽ1v6?-o7øy SwǠyG?.?L1UhJg7Bۢ=vΘn]K;gwcyS)Ÿbsilƿ\N__/f>q.sBӸuLJykI'~0cB.a]+uzc督Xw<~qw t&6Ri3=ܮ㿟o^EL^s?-f~}7aꆛ蓎 }fk~枌H'OYsei(u4;ʖ+) 9fq\*MlBpJn ӣtj@YwGj5ΞQP!ULQYF2k-J֡:S)}@jO{- o|zy5[TV5kcJMȚշܽ>0N;W7yAI5hPa/Fα 3xSoKAд n=4!1zj h!n'(`௕{u{.[@.m8#SYcQyӸuNoHS1O qIZL|v Q_6\BQ h?+l2?ޤX^QL3F:M8'wˆ5PA# gMR3#NR^8jQGa䀵R^>4޴ؐEq(1dow92RZJ4+xM8*Z xr}xcn|j%)y6?ǣrO?xm}E G_ڶ6Ŧkt\ NMIMk= 2P!g41[xԾmC`%jyP=9c q.C0rwVnwwꥻ]ns+-KwJJm\gi|A](+:Jg>P4i:{|^uyN{аjܫ5sY%4-ҧ+NZ)y㩻Li׻E Lw8u0 {*K_KhC67;@y<붡m|TjTcЩS_lQnϿ R^rw Ip&OKqa\RM8k.D_dp85t~xc֓4EZhkxtGtL7*\j{7*?߹3%ZFt8j +$bqywP;^Q3Be7YV3p]mvbh ~-aZ?bhae3θIJe^JJo>cxK3gUsDkǠRRkىGi塆lKá)VG"zr&[ ʕ@-i]TR8VJ#,ޑZ.i~RQ$~l͠w_;ZQG;z{b8^8ZotɪWwy냩sS2cڴhqMOҹY'o~:E]&]/nq-Px4;奲wOx;q\ݺ[iômq:T'aGXB(kt3KIC[7s}\+bU2\o_FeBX4>Y@2s6q҅f/yy{OsQkWe22z}r2^o*5vC*+em(F?8܊OKcۈ>hD}!npY1G^a q3S툛},!Q(daY <45D!ܘ\9?7˗ṟ5_vA* 6-0 a2K4&pL2EmJezP@ekr cx׀{WVl˙zEO&&J6g, !!(8r~ΨB7$H4>j7m1%BbcBk_pFVP(L{Sp{z9a:SO$qR:_s|A>Eɝ[;v']ZsqQZ1oϕM?L" $߾?S΁uVi8rp4֟OAOt\8iW+PҊe^J9m=GH}]ǬOr<)xѱ}ƷS#[r7RPҶGU¶.C!z}v~QRvU)f*.e]pV|'73O_S!6/-СS^}TrP@N?" wDno2lenI8aZ\ӣoqj<̷['q)cv߰ÚfMXP ҁ]Og“aģdΔ/\.<K[ntRʰ^`[.+ކ|j6=FP9g~,}bЛL$DĶ?yP6RO UzkOox<}4ŜKи}V- !4WJ%j\kuY~ӻ\M+uoYr q2LupM]h9oN[K7}xC+f12R,%0{!~ 8#0=E&QvxKѶG7ǧMmܓ;5nmpk׈f$Q!3@㵙Ǿs&mݒ*{|[ʸZ55-Qfc}W@k¤UM̎SmO0B4n!/6iUӤ}RiS9[OTqi Sff'YH_,W\3#'wftYAگ'߭&n[+ʊ4aSJ{T?-~lx(ȟߧP/dִqAwc^/FP;R /*MZVYaGN}Ѧ{?;@N!oqwb|{q8Ra7^݋?JW|eWtϖW(קsbO2Kt*ȰYQPȯ]qqin3_e,3rVo$}֭/ć]c0qhq+ۀ=ރFsvⱣXqjA8 5Rә6u<[`BQ`>Pd4fz/Խ'޸TaeIIfL=NNm_7u87Omë́NEɬcA7h4ZJ*ͺ+PkԑCd>4)4>rSjumgB_i4<}&>n| ;d͛ء"]gz3 Ias>sM>T@||QQM&LzFLDEEGll zB;u?WN%]O}%ɵ&!\ʶNBQ`FGsֿ 9räF˧h."v BXEXg`0js)Q!=B!D=zJ%Jpwȑp,!BaGa(,쩦xg!B)&q9v4FB!ͨB!VgT!B،4FB!ͨsbIab"{ЩscR$kÉ9/|ġ,ĝclEYdɉ%t kaz->n{ KX3(Kvaٔ{;Tݟ_FIw{nrfZCe|:w1OI>DM3A]} LȕO3?&C!u&9KdG{ 9Æ7 {b-\ۊ '.[!2 Ĕ($pb:nA]ԃeс{$wl2q9^Ꮹb.t { ^c օ]άN'2nu!őLt̳o9IXS&Y.l6ݻ9?bə !,+Fa1.î̚띄 x\NU1CkS8.H7dՔH,4Y-_tq?tw׬!_vBML3ZJKjߧn϶?w̌㡥&YGwpҗʄNhb3㥸uČWqΟe<_fsٶ+HO,݉qR};1{:3#Q'fi1sޮW W¹~l D .N#Jg\Ɍ!q|˗K^gkep<͊'1~EY+wW75/.~KfO罆qY}N,cLD!g?g@&V Op~/LX&~AL'!]N /OqPdXmO[X/j8&a$&.T85!3w4x7H 8J]&thH칗8 (kKCREAgd>֕bN\{zkD8QD  .U!fL'>j $Qh0{O=LGpZ٧ S煿k'o`7vn;}  eY1'ΉerwI=\#J.c!DQ9Vϛ9ZǛ}*XY8UHcNZ$@݄\trѾ8@Ǥ~~VI;ތtO>ƏFʃ\ &': WM71h =38RS.hc榳0Ąpv:KQKsFPO;!> " 3>߅ѿ瓹6j7P{'hU0>EL/4-դM΅g4z}|ͬx|?N+ v nxHhC#Ȝ9: gΉerp_3 c b9q:_KupBSToTlZ[ؗ%NS4ƃ>\~-dR??d>ڏaV],]qK& lft?v.jJu?o2C %AF/֛.{wg׭v;q{?8(٬#FY ˾]{WbwJ*7RR-+s|+#0d-6kW.v?27Ѐʹ8:d̛ SnS&܅ &{bSʖǾrX[&Y("x˄uxUi _~F4B:^Dwdz);vhӁNЩYƭnj{"uR??TCCb2f}zK.ѵ\xS_{fCwi/RJ|f$0 ^G-\BmORο"kV.;FOQ3ğuBc ^ƤI jGqi=9'B?I"yšAN`o$&4.QѿOE9BIՃ%XB!D&d^!Bn%qEbbbQ ?$.?,?I5)88:Rb%q$OwLJ{t˨8&ތ  <Eą3Rjśt76;l&.DkSҁ.Z"_GDJ,K!rK”*YKg\'ϱbw;nOy >\l!DvLN.eh䖜s*!D99;CmYbٕSu,{yj[S"s"0!DnȬD$B!l&_4FOckaؿв>@!'0]MKt2i2lI|IߺaQ%S^GFxG)n5gF)S#w~>Ct|B)]e0"Z.Q%R*Iu;XAW(NRaXSK/D~/3o-_!Vw֌c+ݨ{> ]ҹ,^ӽOL!lx)ݙ c1xO_F|-إu 2f[(2N!gU];OͧQ={fsD1e'p86O°^_#1#٘i6c)S"0 4~laLPu| aW(ջ|uf'sߕa{Q ~2ar;nzʮK#t1n䑉Bdg3ʒ7aM {AU7W^_+dV\IN5^u?5ۗ-4-Q]etlM7ii{`e'UԚN<w*\'a !c6,yd<*ԺXGXSK/D2rN?Oז˨L5jLv0q Mx : >Gx|NqI_kTc+1lUoIXB!cU]"I#̪z]~!Eka ygi1z /dBYQgBfu7jfM-usz&#;?. F JxCY}c$dznlNޱ{n''C5޴ޖ_7p?Z>>_ޠ%WI`Xqz߽/^þ\ȢB<2uT,|CR )ۧ> <5G F5n-'tb#.MKJeԐS/pJƵ,ֵcaEKߢ]2Wj63?^a}vbr W4R}ZR ꀜOT!)w`4fYzK/ӣ \:Ű_rOewsĿt'! 8Ka ˒Q$W;;;['Ez=׮trnj\U˖_Sobm^!BHcT!B؆"Q!BaCZҔt)~(,I q\xEtsMZJ%4(8< l˨-6hR9lan.8;db!D~܏uJBL ,?B@)(=~n.4~-DAߴB<o9M) ZIAjEDdn(lzW7xƜ<,7h2]Sd-6(?1HLdxd ͖ō2 ҆_uDhؓt)Z>6g21< Y޷6Hck-O<2pXԦeh3; (wO_V*:D!Jxdz-[6ػ#Aۄן9+3ʏ;=qY ­<5M"SV` F;0f$c,>Ⱦ ^LZÏ^w1@ٱN-G\'ٓ&%t&ٞhɯtp_S=9 ?>}IdjZIwPd*"27oSϷ.沢N:ş?:C\h϶cG'w(IF"_H_h"2{: EED"ޚK;My\<Ǚ6EwvlU:"sT|ncFJX\/ PsDs?ݢi0mNzGw";ѕ0$ʘ\f$@"""S*z0zeczH~^a>_b ?;]s*P>DD--'yZ)r7bu1ʜ ,eYZ~mo__SǾ3ծȷ\L<.ޜZDbݜ~)"RLΊZc dilNjx|=/eG;~sӚsW•SfTD^4fTDX)QAx=wq j4?{/O7ڋKɍʍ{[3̿fgDD Z }FcCMA#Ah;n4=?L%?%u1obswx5s73t#7a x#i> 3)W:k0xo&EK;HYku5YH3x]2V/[5֦<2v?IA] d\ $j5B e>+B hM/">P0*"Ee :u.X%>?`~żfU/Q~E.N|cSD=9c\>eDdn7/:sijbYJlY?zt4o7qur3dm)v"%wp;džҤϗ5ͭ,`-WGr]EB|ǏʌHQ1,{V]_~ _lrѵ\d~z>ElCQXM3r6i?1չou\:;)-+} y,쨬ѣk|7]j@Z^D ດy橧7SU08wf졇~wΚ*B(1JJ\wQ9\ץÇŦ͗Qgcxq< FEH˳^D8c( 8zG ]2Rjhjj"|W~z"q]2"0uu5$I$bQJKK#ʊHQli'EEdsq]H4K8s9m')NFEP<N>rѵ[D#QfӋHOo ZeFEDDD? FEDDD7ZgTDg LPjQ)0x0`uȫt~&%dBxq%yMpHtPp<"sď̨I6qZT:ȠM B+׬aϮ]dY""2v)եNmg>LJ9$nz EKgn*X+(YeFEYvӿe+Bm]d\ykXx@\>s;ZG}Қ-Z.2n ꚣXTD:_#+όi~+D$FG1`;8*sފ:fci5vIDATqc QָW?4$[w vl@,H3[wۼɒf"zUnk빈OĢSw.pTDO`ll6y'B: FsYɂQod!` ր7}T\Ǖ1dac6EÄjWye73={yeaU+ش"N 3@랽j.q\,پlݓ`"hQNfŬgQt!͵rm2ۚǮ9?KS/2sk]DX7Lmm< CadT:M*q}pb/dݻ: 75w,Ƥ9ԁx @i=+W3Cr.$Yu%]7RɢdZQ~ugubl>+ְP@xR_$!Րli?{pyuɯ= ʁH4^DeF%HK(" ?7Ϻ7O^|1#8`QzAjsƊ ©~F3 cSWyr'\J40DM鯇3$=CKПS[h68ȡ ֲ*45GRWsnpTh0]8뺹;;9fkX>X^O#=]ڎbǡpI (q ;C]g)fi;G!+v*:42J|aq亝4mcX`(2` p;P26_KsK唱zC`x ֍gq,^US9S]3x=\ʊӛa 6/Tkr?Ozm쫞qHįB!V\w3DDRfTD twvQQQE_ooزu{^6],SQ^M{1܀"R<`@S RzwXϿ뮺۶1\r/=UTTEOS:"Y>w.D̗[gT HL~CEy}-g`qqhuO'"QMPTDfks?ȱv/.V$e@4u\dPTƌHQKyYpcsC,p$j̨Q)ubDQsNqN"R| FEȸͦgШ!"sAYdn'+QWKpӋ_:EDb,4LoX3*"s_Ddn(QeFEDDD? FEDDD7]ID6""sC郶,"AYdn+B(K*A#FE0t \g4K"R0:EDbΦ/ll{"2j̨HƵU_h{Qv"AYdnfӕvr6Veuȫ}Ls;ŏVDDDDG FEDDD7 FEDDD'M/"""">R0*""""Ѣ""""eFEDDD7 FEDDD7!""""~QfTDDDD|`TDDDD|`TDDDD|`TDDDD|`TDDDD|`TDDDD|aQQQU0*""""Q0*""""qnM/"""">QfTDDDD|`TDDDD|`TDDDD|`TDDDD|`TDDDD|ウӋHbPeFEDDD7 FEDDD7 FEDDD*)(((((X""""""""Z """"2""""""""O/""""PfTDDDD|`TDDDD|`TDDDDao/M/""""~R0*"""" }2k۸"unE>ǃp]=IY9 eՓdu(wSHסV9麗H5܏㼨[J2Di9Kv$ӗ֒n"E|uYj9o/G[uA[~4⡾Zm"r%@HbQ$/b\ڍgC#(h.jfr{ˏ{""R@ppX=\"2{M/""""Q0*""""Q0*""""Q0*"""")X0;QߴPU9~BڎẁfKb<\~lmsĂ&*jx'M$V-8{wd;ۏaF Sz z&uFcD"QZDDDD|8S tYjuFEDDD? FEDDD7 FEDDD7Wy(߸V""""eFEDDD7ge{) c,xd3`(|_4csz )l6K0q xlU 8ڎͤ [,XEK䵏KL:1:QsPqXbt!p8̊UػeG0\¬Yt:`[:λ`-;wl'͜LG9hxh.XK:$Jߤ дh!vrrCC\y3TtrbJ%FZ._Ύ-/LG9<28Џ{d*IEE%C8%7JK˴,JP^Qsc3-kk$IXk\Y0$"Rk`&Lmw{8K,VV9k t`TDD &47rr>ȑvFHy@ Liy-/Vx}xMRsJ9m|o*%8d3 Fzw 57Moqym7>C[AYz_z_8Lo T+ Dg7 y?}owqes* FEDDaڂ###F.["6w[k-fBKl:VEq9IkVx?ew=!s3F,|J7\HlRs7_;}6p/}ru4]?WZbgE9/D"ׯyp_7gM({ԯE%'RPeמi+YǢxi:mfի/`Iy+I3̨d_ K筌cd{7_K>CԿ:vpVOYoj}ʧypuo}.8f&1vYbɯ} FEDDavѽ!pE>H䯟bKJ& y l,XMcܝgE]7iXYN56L':Cffܸ|?P@ '~md{{o\r!E/bW՟)jaw#;yM3I[⬻& ɮۏc(\okYs`q?} _?yACzn (;SnI?~{<>Smu%\ѻ{|_3CP#7A 5aooΣ7H&yWOhb0 >^w73J߰KUCL#Օ3-d0~}}1^3Noc_l3?e5մn{~7*=t|lT[T7;~KSYWSןn%\0_M~掯5kyТc_(K>ο}_ w=(w> YTksTfTDD./xkn9BCCdʊ :;;y~{ho  y/|M6eQ^iccd<2V@i3Hͤ~p3kIts[D?8 V/: xm'W~{8LO]f#\]׎&>o:y/?Xk $طn|RƧ]p}dXf`\W޵W N;V?mwCoU7P;!GR]]M(vjkFx1Çs%/hFN-3c]cA)h䦙Aw5 lJ9.>ć:$wY/=^N]1XrC>V;hNg0k0X.j"xJV:"Mgʰi᩽\l ^Z e7Q۵5Ԍ iSnz9g$ӷq:yy{&P[W8$ FGG.r"nD}t&aKY9d&<`KqZv:>ܑ.bI( !۩~u<}bK<-Y:u2#geӄYv-|~-4~o{dlEuK+dn(BK5=N>/Aoo }]9IAnyeFM/""2dF.ۼ}n K:+. *#}I"3D^|!<[ʢ9xf)k3ʐdFs+ ;r"=&롼;Si˦?otO}7 rش|u ~D.tXn8%/f *6GɎvRSű&JhZ@0;G` n~L׺>@wXul( knҋ6yww0ǂ嬻_Kt$bcW`lt2 ?G%'ք\jǞl%yش_m&sO`TDDfǖ*.%:8p]v սW>H4w0`ögK]o摿@s_Lj]? X,elzIe~zFdFh~뗾+3؆Yja'sLW^΁C9Jjkjf=/TȪ =j#c'H˨ySJh\í4 Y^caUm&4\Z.xʲa @ki=.iyb?ΝK~ۿz,dyϟ TpmqoXt |uGs]ر}8v{{W/o;?emXoݬʄl ǾI +/-75MLwuvwv`uoz ͇`Y|>+-'Q1/)9nn++-;g֝`/ =t MyELߗwͳՌ)/f:9cd$+_C:9ЀEyy%h_IJq,uO=RzU p,xX8<鱙x7Qs(` [w!u5c89Ν;-ZB$>jeƍ,X0a>wvtmۋ`-->9@ @M""""c,CCCha䋲Rho ם|Aw_TVZJmmS3F5fTDDeeeD!jkH&~7hҒ8Ht`U=oW1{l`TDDXH4Z{9-'k&f*܂h?_h:رkfp-BƌJHYyVQ3""""Rt&U(*""""eˌざ?b3jȌeF&0HQucj̨Ѿ}"ZgTDDDDW]͢ŋqG<ݻUDDDDKOOv݇{(M""""2Xzٷ?x T>ՔF&UUռYC/[/a"G9K$^¢%Kعw?n%;MLRnt3êIENDB`kvpm-0.9.10/docbook/vg2.png0000644000175000017500000051156112770324126015643 0ustar benscottbenscottPNG  IHDRD G,BsBIT|d pHYs+tEXtDescriptionWindow Class: kvpm tEXtTitlekvpm*;\ IDATxwW4 {K$PeTT*U]mfΙsfvzg{TUH¥?όȈȌ!ߗEdĻ߸?zz@L!XLPy <{T?/t ^>B}Ggv"~)ZIŧ ,1/ ulᏗD.TUENNr]z{{Qzw^F0 yuxPVۼǑJOeU׺(toιQri[&Y8a 8I }f7|8"=w>Ip&'DP%|wg#WP"ܱrH26m,!6(x *i.^ 㗟E&q=>߾%m"fJB⑆ ?wQNf6WͶ"][vCW$(p"%(ł"'¤H!E{HPl0R텝Iߊx`Y?SZPP\bƟT%E2XG)9R J!Ow:M#<\߉V'Qqu;\aw3(JTLJfe\ /j+*/ ݦab^˺;xp³x\G(MC7qZ@0H<G7t Z@CpD+ԲHy_86w''G{ZyuM:8je8n",l>]FAi-Pp+x!%w֗ZWI٘y>|_<@K2DqF%c0 3TƉE ㄌ  U,-ՒDycccTTiZ NJHH$HPYO=g ,D2H@OJ9ċ*]vHvjbA%cš[ ` ӵiBij a-չ8u4 ]7gcT @[D%QfK*%bٮ~9"Q2]Sn ԧDe"XIpdm˒%)ZO٭sEk䇍Id qu,ڋMۗE X42π(ۇ ŃJI&c*AV쥣 2z.XH$½{94/0> ޢ膑uI=LLL Ly^&NZtťiQH2;+DJ잝ȲOCH)tď)\#>>a$賵=6?e"K+v絝?uoMl8h|4wrv*:S{?PE\FqD%cŨۑvc+Wa&<@UT 5մ47smm464p޽:g#UJ 2;i`jjɇIlML< cFN2MӘx8A !B2.}8G.dB{FOYq5vJq 5yXW䣏9qwc Rx01U9wm\@kf>ԤfØep6xѡpUU kLN039ƅgQM5*7oGy\%jUy3eg#ij(Q7xh,͛㕭29;`&cwx$||#4w܋ٹu=J a磣.Ɗ#*;|meGEy3c!ȕu7;QLZ(Czl*!aOOp({eb|)ϱwF3:߻{ёeBڽX<< =.!Ab -}B !&=R[[xh,]ڝfP]] pZT]کDVQle%qxTZEBbv(JgL\F,dIZ-u)^maxSX"I ӤͦəYu MT;p@Uo%%^]9UL, KѹxtCܼzԇt/̕t6Ӿl-_[E!q^~|@U#P@o&bgrǨjVYOu0~:~WhJ.%uҙ#J 8s \[7'Uq` WRPW4L {Yae$Ұ3E(Tq&WSj{l]!$1JWT!X,>=sy/80Bd6cҞe<w[$,Bܸ}/.|JWWm47qg(F2z.Yk @"=u||!¡K1t=nj*  Ԍ*3D$$e}t J8/+?H bN8K8p\|爋ftǽ"yUA C"1z! =Yo3 즱:Lbi^h&?녟](*JB'`2p4cZFTsmnߋq B !L%>FrIRj\c`:M+XL(6}H+\ڢg(*Z@CMَW)JsbP,- ɒpqLSIB"B ?zg}:ʥ7dBB%cŤ[UUj^~3Y^$[qj1L`P#򠊙Hh,N8@0}4;EQBA&&AUUUUiLOpMKԄ&C$$>ߺSᛰwbD)ɮB Fu')}%Gz|"G/I"0 3kV.I`1nߥmI'{di,r>+p $5FQ%ԇL_ҥtTCcmnj^'˫LބݮIy3ue9RIG܈yp!B]EH*=XDŕRdS?uIHH$PX|1N+!'ih&ԁtuԡ(0zo;weZ> O `fzq>|H4%Y*LCCMM֠r3U y)Mj#E+]CG쇷nEId$%2T\ ۊdIB$H OK]mCVoĝrO ABK"$ Xd,t2D 12vގ|mx[Kgi$gu$L> 7`'"8!!!!$D$$$|/DWH %Rď'PIz'Gm=/40g'HRת/+R]H~,E\6!l]|WHZCE+-;s^8EbiLQ6}IH<B0<|K.۝GI8fut/.7JBDBBGHdEyeem1G/Şs,-onھs  F?}OY$%l%S"֖ ܟ.5os<x(͏#>`nȏMJL<"$HB B|$B$'nݺ͍AKkK+(Jr(.nW=~s΍obA@`&ߜ+@d){NCsR~ˋXP# aPq{Fs\#nǠ]IϬp0P9#zQ?fI8OJ.,QiRsHyQX.Op'?.(EI%3F]P9ĉv}lWcMK(,Z~{hpڵO]]@&J4I$H(Jܰ$Tm2֓N)a3"1 H?KեڊlJ dw)444f]8Ò$!"oPJj$.yt]9RWmX5tue{R-SgFm ,1T-itU5Q"S#?}ʲ溢rkE ׹/L=ؗǸ|}ӳTֳdr6mgN [(vަ/c=0\>u/;vU15G_Iw|Og]x$%N[ŅگAǶ|1@ʼn˂Lߝ](,8q됝zW`RV\ Thù_EI˧+1<:$HB#x](Ibn*Od&ɘ>%9 D$5LɐJPҹI2~U ~f%%%2t"A2b$9 M,)C$nf# v6,Vϋ첅0^z/}ffpৼh n\rRHA'"w EHpŮ[)]mj5.8$m:(mmYh3ibۨ& GQ*5ZaSpnÃX_GU%oZB_ITHDqDj^k&0vy M" Evd9 *O(Y(3^29KZ:9@[ddHZ=DnɢZ'#~uhٺo`|2zlgoeK*|0y|z_޶ty PϪ X3G~KyeVt6͋N"Tc}&G\ue]K\VcTYnfW -JFEqNض.FܯgQEۙ:p_l/tE+ /bޚKDBQa~IR<ơ#%4IUٙ!%5-> "ANEFH%bqI ybg-$@K|tCwTճ}3ݶ͇Pƣz7L ^b*Ί&&C_rNv<[/Gi-ܴ>׹q0']%N5w¾]p?srvԆi[^H`p):6neykqQO>7ٗ{G9~2Q?$<6}v7/1p0']!C{w/O=,+Z#LCt֝ؼf/%AT_BDڪPnXmGڕv{^V:z'/~]PIf]8gy8%\OgzBͣwg\E(!m=k/KWvcm.6YLiNd*&92"=~|;cپw_;OjÉ}<³j-=]n#Ku$b- d*-ں? h̺p"L2UHEa1[(;ҮUo-ʊKl,}N] ߣJK|wtn6:x,i}Uʚ,egĒIV-)Gخ9;_/6@n,^\6.qĦK%q F|"Z5ThhBt<!Z-%/m#sy:ׯ*`t,ήg:h[8tLA5S܇@-W,]Bm}]j|nPM1;/7dVZU}%>hZ֯[Nҽb/i i19f u~Łp6ylTm.ؾqkp÷zKy/}+t7ջzΧkٸ//KE-vٸA24:]=d*?]-A7M3pnж$11!_;K0HzؗHD ~ %rTr$$% "@+jb% 93ʲ/"%S+Szt;lq{ Dpqhۋ1vd UzbQj1R aK2o [xJj_ա®16V \MUٗʁo6еUkֱ~Mom}l_o =}kDO㝷HU&!^&#}GtAuj jjMycGHA7ԩj&`Anc0\bĈ'_(.!VIPP"$YB8@딥'7ݷ&:?ޱ~å; >|W=4RRIHH4Ql-0Qfoܠ kZٰSgn^8G{%5i_BDbXܨFQT`<:C+c:Fjܸq -'Vv;6^C:3$?=6*P˯_7b߿a;K}}TUqg|K Ty.}b:.N?EѨ (FPg?6u7v#)%@]m3/c(L_@<:Ђ*iYKT$I(ɫ()f/GcEnooT*kcx!<> ,y\n9GJ  H4[JBo(D0 N ` mabV|j&\݉>?–kij FsnH[fjCiQuw<\`rE&AgK#kobqwnPnxi.^ֵ;дKشuze|ϥ7s/=6g1 뢳Z<T|϶;` K;tu8oMFn\?N̤'KF ԰=̕#ldǧxy(o^!ܙ8 'O~2}`)chσb}W/f_)Gߓ)3?7S~G9|Zcօ]w,W_w4/}v摒ׅJHHw.CAbnH8I(A6?/?/LW{5Mݬh?ٚ.z3!6/>kbOslؾ;$V-o3Sccܞ-KA9cо>LjgbOe"ce<`58xӑ O#OHUxWopy<0 \8}(P.f.%Υc N\l]1:|w:@4T\_ 䬯+/d팞/0rQ_&='ɨvn~\b6GE5pw|3L|\c}/ &;ʙ$+[)kkոwԸ,l&&Vx}X$S!2ZV?㥥qo]*v)n_"\TX^/ P E)xQ+՗bv?'ݭo[_Cî={h.` P/7!Zоl§[},+(,$kޜ%a|uF~w|1RPۼt,#$W5W_&5U1e^x(j]?ˍ1ں{yWX@ ]9v$1$aŚ 9P^~Bͼor|6?Ɏ-(KUڳMoQ[kYnV)'Եk9AB(!znw*|8߼{\ˆMeT 8Ǎcn-Vv6¯n ⯼*vcP~P-734(IAӥ;NsV*kx~ȧJ+Mc[E|+J7Eb_WA\zްsD7䈜_" ӰSZL>|7nk?~+D6:_;?|zk<9Bݐ|aVn,)O]·J)aϝ9'=.tY|BCػ~E b#9;Y Va}oTXTZ~L|8cb{z (B}]dHHHHc|ظ]T7ɇXmu\"U6e?rn 9JLɅo$*Qbm5YRbÊ) oN?MmVc\Us #!!10L34$eRąWIkJO 7 X^~\W<@4L]'J2;KwUkwӎwǿ+Z_I8|!މ267 vb?yֱaux7%6o`gٱi|-n.1LY 6w1y@˄ VJ>JߤRćB'Uq87:w5C1g_\Frr*ƒ,ģUIg #NNgdA6i&6 %Èe/Qg'ҙyĈHe"ٌDjsmS %Gie<E$?_)̴Bz+!!!-z)._q`n.ǮH?^p;. >'e\#nVh1$?$$$` '{ Mu6Fy1E!Ch$)mPlW"C$dyL1T]vH.B/1`t|+<>zUQKf$$$$|ՄjlURֽx3.i%c+_JF9AKYoF~'L@[U- ϰ$?$$$BMY0M48,AĆUGLzdIkfC`ITX"@%4d%(\*N05MI=7 KWG8#yV"MP:gc\!\ <E aPl<#C|(GFGvY\6CP=DY "[$$F2!S7%H!D(\3Ej) =i%#(hpA%!"!!!1OIPl]\D KLD\f!Tr0:琁"ļYL;&=3azA A"`lr6Q(&Ċ%+&9Eĉ&B v2%=H&FP,bDI"3I@:sD$3J 3WifƧj46̼U%!"!!!`7Q$QsExV\uµ;eY-,,8wqϡ"=JHHb+Wƍ5ܝ2KFH%JC*{FMfdXԣ 崓"@E*C%j,  OQ*5_Z,D#6,)^csIz$L,X8V"i(y{XHT4 h4ZPL D<W z3g lQF,X8\u1,Z_|r+!!Qļ!x7q4GdIʽb'EAȀ1E0 FE/X$!"!!!aȒ6@ yv.+EA¸cwqw~N,13HBDBBBB"bd OX>G/d8gpEx0eIxHHHHH$!"o>߮HH,hZUUPEa~mbZ;-wVd!QﳦE6 MDHHHHH,$n.2CD0 EQhjn& η; XiNe1^y'P|r?#[RqXpE'`0 LӤ ]׉F"䡄‚h`mP@SqR n N 4;,׿. W$$$$$$H'N{=;;Kgg G鿣.OV8`Œ_)]uEuF&oO?:˚,oK? ^:ڟs~=6?/2E Dwr6l@{cmEB[Mw^T/%_B慣)O>KS]- q/xɿ) 0@w !*;׹qg]-2cC\*H mgC_='N_g3[P,g t`IcN!Dz8GͶuB<+Z<ų),[&5ե#w8wcg13ga۳<ĝ7xG/hp06ȓdMp4V_~αsW kdsO&ȱӗ*6R &}Н1U,{w'(y"7UW#DL LS'T=WYq~塮R˫2!y/f66mKQУ}4. fTS: 23ٓ9sg:<`0=gZvo_G}|~8#'kٴ}77@Y#4BqHy);peVt4Pv"CE9}F|~.M3x<^}9d&2yǎsu`hܠM[wy.Lr1._t$4wauی=`Qmgy{_B8v3n/:᠖7p߾u/KkvoN.u n4c?;מ% 45[7s_rZjG9uvm؅i]+S{\y LwKZ{h}wdWta%DZkc`sCx1]SW8Ԟԇ>O{S¯^cDBBBBBBBBBBba!RP0۟/.gZ[Zhk>5{+n}x:>͡3Yӎ#;VyP#|cFw?|Tt#aK'Һn+;7N>eҷn 1L3ܰ-k'BnȁWeve#TUG՛VG_MSu(GMr5Vnۛ"5p+Ԏl[:3M,c1ܳd{*?Jz-,ƣ j=?7?5Z+b곌ٽ+GJ>?e*Q5@-.ϱҽj5ϙi ֵ,cEN^v.փ>jS7ǎ]`]w+AUE`2tZ{/Koт/XFHU"ΥgڰFl`-kWju^UPŧ5ED6٨ލ2sNh' Dڸu[߶׾=Fo sk.p6.6x( 9o0(ٲ-  -KzPNe6nP*jz{-ߤag8{8\gj6.W:t!0##lBUst.-X9o@\g6S( lW $uas0s$ 癠g{;Q̩]ӈp,[jtc7Zp)짹2"B%WJe|jƚty _X]zI3M.  Pňc&DONi[j mB(Fa`c 2v%ĦgعǮ4x<#s19pW4[> Q #iWUT ƕϼ [ 1~! b:\>J鑔Ka: >E00AMueG¬_[eUsuVn`=+NT±Lj|~_ IoJV-%h!Wjvp-w~(E#P hђBh*u@.!nfl k"Pr/W.{5I>Fc<׉=G9y:soj rC5l\٘WQ_dXel rEQkJ_KMS<ǟCxGW^Wɀ?,o>ϗaa~:/tdX6MEm<片6,Gy=\hȟɟxUB<~R<^ef&ƫ<ފO,R>g_c:-lȷ2beNp9ǟ>~%/{/|aVw?ʏ?ɹv·(2[ɧx l\?|U}|T >̏ȳ?73ʈ~ -KC?ST (E72j })~?l̨&쇞˿)>jomٟ>o-l\ǾY,6 4hРA 47a3ܸq\}5j5uMqAZ-z9qsZn >W/?r`k _ 7Y}{)e˿Wܜ`>Ӽ| .>|>=qe|s/pm!}1>ǟl~k| _[ k[y>\ FZvW_㆕noWyΡ* c-o~S_} y2:E}MZ~|QǗ+l_W"W߼I Aǟ#k2G{/Kſgȿ,ZiQ_o' /sy:e 4h]A N!%++_{큵fx!:) ȣG" Fw~8]n{(/}󼼝?}GdjpP t\?9Y.iРAgfРA"DkW*<^.$[_"8໦)9sf~}dTe~l_ݢܕq<MW2ӠA c 4hbR:YȸSo%]sk[<~_(YZ7n|}oBaA[䝆,)˂,H)+0 ]J)6Z>S׻+ʲiU[)+( i)oNT '鷉;ZOjcZ h_24r%gRl,KX\*dQe~A$IХjunhH3`ZJ~c<Kħ.~ؠ;RQi W!');VxC"SEq C!EIsʕ^٠w+mΛP!y/r%c\H7s|IdGc\pѺa*K3J]zNC._aT{}ƛoP}tDJh)Rk4 UׯH0Z w^fAH%h!Bv&ys@] P:U\B(u'c7@3cTo|x"RQJ?=hXkɲ`1 σe^ Fz=e%*pdTgrƎI\7AX'ʢ 4e+5(Ɏ6 AY<:f2x]z]p 9y IeNcN(@ܽAkپ}8I8ܩ!0Njˬs/ IDATGOy::]Dh1VJykosX][E#" /Q5;w(5HEJ- ü(PaFwWf,ȵ^^nͥ8<}dYh8j-[uB"8Tm4F@ \Z.Y$ns'!X1m0a- 򞼃~Cqر5S"9JB^Xb1k') c c IZkl4h)R9CT ~+Kw]¶`|ڋ<O3͛cz/ujp2v F4as  H…q ,(v(u[C1 @89]^Xm2h8$:.Y)("e4 ~vX_[_zBjX< #O-pP"&YˢJ)BYRYQ%Q'IMxXOXdYp8Ĥ)Q qbѥ&sJ ,pUE^0Ҝ8HDF"?1HIɸxVB;!Ng#yIqaQ$VTkxeҸOsL~wiI>+tWX!-RO&m Y[f6joˆ0N:;wpJ!*x }䑥t u⤅6 FkʒSEPGJ;BԘZpr0ZIk~~8L3666[nyw" uѐK.O:3"Yݼ(KkkLr8?qn+:bTx{%Dqow DMƸrm3!eN)Sc7yOOS{ 2duuk-;;; vMNVG7h`9mz21MTK ۯ~NS Ʋ[7o13[!ۻCzrtһy^rid?/Q $(%R$RD .Mke%2 Id7P+CĿ,5*jי6A1R nnɭZf4#b rlsSH R&UH)RxiTq*K*&7֒EQJw zl,JFiNM,) & ሲ(!ҕ*%R̵ZDQ5hS*2\&QI&T!֓"B'ׯ` &K"¦&ߞLmRVHkYI5qp[<#Pd=VXp (;ra#+2!ϵe- H$Ah ðBM#c0EȀ(ۜ;wաns c%i^fgOi n%ƒƑZ*)G9oF kkK_֟3}I!CEUL= 94XI^Y"-<_q@8XGu`澞+ e@HZa󬊋Fȑ7]˥ˏS i!klaNŗB Z.nU;"a#)$hceIjTT;&?>n#ܘeR=SdPIWxyX-fjjZD8 t5S<<1XsOj])Kc4ZJ[{/(J YeANqP/>S.mڭb,ϨP`BRgCEU9C_?QNj'I08Ee[ p0yK rs$mו[>=%!2VPpjo O?Ԇ2YTaa-yJHrV# ݏ֫R}z^$'GwE> IpϷ B:6XS߈O]-EQ#R: dҚDonP1څGkj *.ncAOA!4h<|O!Q+B>v#Qg񻮋1^o-`'^;;;|[X[wFdaKHdWJe>L9!pVrMᴱ{MYDQL>Tܸ~pZ.]ZK@u\I2.E+IYzuYpMJ)$U'.g\BMG1![e,nUs{! څnVEQ),|bIx_$)},eEFwt_(}g5H* iyX$-Sj#|L`>U4/ʡb7M?w Yy4ֺIǍIN/$: !n9I[F3"tJ ǨBkw3p..\7`5͉㘨A~TVc'cq Fk7\ #iN?- X\AʕQq;O AqI[ I9'ӖPoΪ/ZH cܻG#۝Q9b\}e66:/\Z/ބϱT?T`}|kc~fwwTπb fSEP:JkFyIi;@VЮSBB8r̆C% 4ȥ/K)Ij%Iz!RQ5b@{~ XVX[Յ-ʚ:^x5t0t4"RtY !ƹmDԫwZe .دq6ڧ>ލ`aԶz.p @ S {iq.JoP*KzjРP1^=&ɐIBC|/ev21B?+(t +X̹g8̺r;A ~ja*lA>v&e=ڱ"[fS*kZv }…kNw/\DuwN0VwyZH0hi($A]<^kING A4*FyL}G.=,Mff)Iw w(E@[^KzN4+%f&k+Io^i988;20/8+!v:+kK]@Ԫ.pa#'EjL+\gAnb6/D0]iyP yAG( W_jhc7釥aRmg| ÑKk{d^z *ZЕm&l=GrB&4}J1 Ube&Q`!7-(B:NM"@A ȲajOĽs M&ntWWA>kG@)–:<խ'T6H(UV[ʹi֥R~UEį_@ XʼaL3&83o?F)J[-p<.3>_e>?. F6Bqz!HIRq3>tJ]" Qe,s"k7 4ŏa+5htZjv~ךYI2> .ۤOsoU~lu:\3a2z먋gJQ Cag?"?|+$ƤpA pF㳲/-'MSzh==/`zytO=GnY+BrfZYrTDQRhHfdvVpʊOR×QM Ƹ gīZ<'fB|(Gh6r5<'$ L-uKOOdi0`muul++]X-EA - ΌN*TX!ĥj4Y~fK)B C_S.{2lеYF[('IYAVZ#C8 A!jGu!ܹN&󜰵Ba Iu1,02~ N%B=D9 "E'܏Yȸ*2ѩBaRXt !Q"Ȱ>;uN(I 벃 N"4ձcI/KwALզitH60 clqĮs,*D>$1k[R1c`{:'=B"V胴P%pDEn0+di n( lFXaLQեi#heA2!@ږqk+ %ƈ)`k,URa `e (e |҉ϱr+3ʹ!q-.@8hrn-F dD'Nj5x(9Bm0ae@}6E(;cC_Ī1loo tx!mrrZ'sIhonդ.;Seh:C{LF!RfcR hɨ7VԝBXU6 q89(:/F ݁$&-l`fk.;w V=QIT?`Fm.g_BP|>Zog?㛏0v%w Q(6T@HUݪi:-mf~.@ǜi2~qF^h!IZ>?&8cCP"&GHm aPvjQn52Y/Pש܌ҽqvڡriJ7 Br3=!Bc}QJDU59n>epF/̩Rk(yֺ ?ݮS F>kVJAfU4l2BRMuT[8-\PP ]Y&XA(LҮqj.i\J e؝qf.T N)q,=kg>"huQ2U': D38znYENj@u{9azTHǺm+ZQzI sPSN%AYτ7ȱ*o#_,ml^xᡪT3X.D0*5jA5R_Zk C+qhwɰdinjE+Qyb&:]Ö$I=Ƌ~ss0BMϣw^%ؤ83`q ` C.Ŵ?>C t; nйQ{&HInS,qCNy2MY:CeXMƌP֏Fpk-Ƨ?? t=HAX] ©:J4֏UEdr_X+F#%3D*O.&X۷Ҍbᇹk?BI:ڕܔ%ֵQWim0߸AwtVP:u tj-IY 46[m!@4xM''}ii<"ߏPIfJV2 k>S[n=/7鳕Z)Y&;Frgň-y^fE^ Ed*T.X$irH-}'ujT8P>+ EA [RS%BU9(a'OȘ)iֆhDg\@)X{K8n'>pFkX"r֔,ωb(J4;|k@adyq˹ @q$%yњKw"4u<lDR#h> hڏ'.c89] y Nh?.ޒ~;#9qX:3qأ\sr̉N0miy,winTAB[{O"t)(k1«=K+WaXs@+ "[?0ڑA| G0vYx4y#r> >ΗTk0 :pUXKrMK(R(%k@WbR%%D$t 48;zy~?"Y,&,Hqqŭ6^;vcH%hZu[IF~,iZD[\ uF ]85Z31Z;5)dB",iJ.۠~?p3/c:(1zB=9T=l(2̽WEQ,.,ܹ!eQ`K)oޥGBQ8Og {rƧuI귻G&F*PB2|^WsecViF+IAP(Ys1 S**?D!2P !l[I^h4.7>,:ͺw8DĜ3,O\YGk9>h„'1ۜLn}͗:uC;bζyۏjv |]?V۹FTs;gdz¶_gj; ˚6afŶs:cs|m}yͭq_螲̾ӻ-H#MźR?o§.VРͩ=$%>PXgV:]nТ&$"G AĵQK-tt}B1| l9cYc!}!F#٧xoР;wCƬQdHQܹs,|`0`eenBnE\aehO(Ka {F#,8r&J?$NH3g> B  nAR]3wVqvf6UVR8 mx>2!$Z逑֤& HWFXt> PA,|_C8T{J #p~ņ<RnIZN=hш(]. hqg ,0 ˤs;CVh4UZ-~?&\]@y!H3$6Ik-CQK!IBkK:ls90NcH >k Y;wai~.Ylނ!RBWȓy| 9GHlզS=ᘣ??l ?Q}?_TVkB=wg8j>8 wOv 3$I~"GG'G]w"ֻ09]'Z=J}7;zC?yM)C?].#?|[AR))h%Xa քډ)$I5,x7xGO ;AGIk K2/bԧ@uw4hpq?p;6hΟ?wfeCM֭֝[lК]r{QeQ(En)^,(bp@b1iQ$qLr~ƙX\>.-R/l8 YZ0grqTƻ&)PUIk6\,cբ@ENrJcPeGJݧDHcڻ@V1,YvIZ.Ͳ]F^%b+RHߖy^aGq7Ekʹ(V9eYJZDqPQ 0r.!%/'6dYNQj-0Ll)\&G$bD5޺Ru羣EJIi4CFt`,°&ʢ$Nbx:8nQ~gXzɪbvC#?wRdȜ2!{T,#MG(/Y.K$9}Șc<2v>N[AqʱOG${ xq4Ҕ0thR.z|(x.!֔.ދTX)1V ͘xW_\++` E"DQ\[r+ו*;$֔>xmtLw p'e *9E6, j;4hpv{B,ﯼ \xIH[d&r;oK,K q0żX:U1R:U$>k*&d.VDCrkLJA`LV{S,tΓBz=>fdҢ>vFEAœp]ȵmH8U1B,>YU~t:-غe#d 7("BcWB:5V+i!N뉚Η!_rYn(M>Tv&V>Y)'4#/J v:nR֓BE;NHD }f+ˆxVu]tz@(n EQ˕WtƢs}6׮]c}}dq91OqNӛfhf8|"_C58(8fI#EV35vbb &ȑ?}Y 3k.o1 Op,K#>"CquQԒ{w>{DUڡ`p˗/zmoos 666Wi(åxGm]Ke{+/}M{Gq63ܷx7X_;n jd~lT8V".!vi-!,>8EUE"Zڭk'L6@ R)t+~":5qAl"A }]8vhw)C/nLk^փV kcaz8|{h(MY[n]:N n-윎RE]`82JS,#c($,5iNEjt;$q42[va00JS %Uղ$2ƺ1ݕq\>Z5xFN9'шy-n߼Aȓ,{Y5c kk^|;E&QN8QT4Wͳ*D6w?CR@!? bdyo0#A~A2s#Cfx9O7͡3.u LQ?gYͷnSOxVWiڼ7p|^=2Yqmy#u5ZƋ?U; Yq6.[[[zЮ;9:vlmmPAzzDݲg㨮{/8M1!K%_BI%B5t0ƸQ\eY$K+ievwȒ֚sܹ{wwν@^!DDI=u9 ^Gn%u.*Jt*Qi*FEE)V\ )v",~)3HV۶m!v\v@io*w 8Opt: {<8v|uT|> %IB2 0\.hX$}3REEȡEGdt]ל#@NݫغhV΄X1.&bSQ}![ [Y&yrSUVRhZKC Ƹ@@_WG7!D8Nl6v !DmwѧȲLߪ_}8Kq8mvlghTNgyg#gx Ěkqy6ڽ^ wd}CnNR~ iFz6!>͝,!eͤA2JFh(RO »p,NNEؾq .YO3wsKFiy {S\fiFWI d, ҺK cV^S.y%kĕ;HFz"C:2U&\s\$9!,o>돪A륵oV,gq[)((bЧo_mB^X,)H(@eAug˦o;43#Ev-e:dӷ3ߠ=d^'V٫gZ zr( rs#ϚOKjniF֨Fin\z]]n^C|c&j(AYV" /"/BЯa' *O;^O;tn(ȵSZSZ$Aӊ$ ֯;^OM~AgYvt ^9jPP>}֭ئojà\pqt:B|dq8\@ ^)͚^AEJ z=` +B}؂|Kjܜ\.=J{# "6I˅naYufpk O) "6[ná2G搓nɟԃX#Puw5pPTPЁs(*(`L4)8w$C0HzUU$Kdg% Rh;),(nҭ[7)(( 77OsЧ4H?1FnS^^Nj`zqHD:rsr3+fp;GLȢkpY56/ջپջP= г{6F2KUU-XV|^_[ Ǽ >6;-- 0ML߸hoS#Q\73)hO=G~z4s#rS :g٭8e*SLa s'uȭKa59)gikNcڤL?P X]ōwDYq0mȁs؋xr/Wıt<\8+k(1w\ިUc'I_kvmNeȑL;8S2j.O=2K\'nݹywF ƈa5?C1t('ϫE3!r\<^`W[e~|X,;gcALc.}Մl6܆o|ٸ 9哏0ws1s\>t0uPƞK^[r3˙5j#'ɭgW0kpF;"Q1Rÿ >'w|ҸnY8{_6`Px,=~w%*O﹄Ǝe~dH?O7ɳMGɐyTU9a)2d` FEE Z ׀I Ï#gdHYdmLG ֵ:VhsO΋O>ۯŢO>/Wfvoes~j{E)9. ٽA" v }O>Ik{[L?B* `0f K4 UiqPѣ:X=lf˶8!O0>òY~,9BNl-=8ۉ<1&O5;ZrXph<鈞~*^rAͼec1%v9Y@h ̿~s?>I9xz7`7Ȟu-~\Cu[ݖ|J-vKV}36pRj ΟciOt<~]jǤG [djBch1<8}qQqd\cO}q/dqU߻o >uw)#i)B\t|լ̽Wm`wsykxy:= 6H,xrqru9g[&&0ULk9f,fΚ|)EϽ ngmt[OŃyZܗ|-1g!fߤ?@Q#?ﻟO? y?=^J[kxg475}%0|2r0?ضaڍ( ~}MLbF0dQ pZP4Y}~:oc%_QK'|0}3gMh%a&=ǖGD/ȻE+vәK)h Ys3n:oǖa-4TL#|kHK0no5.ocGyT ;l.{p V?*FyxG?$T/Z2zu/N.bNh*qP&?m=-onU,ُ+HS?ʰZ s+Sa`ک ̫+S`ijhf/΂%öMe_c]zqeRu<Y$xݓ.&g^r#spʹ(C+GT{ϣ2t-W]>RII00o?]6RICz<g/ q%\  ͷk{q`jr^ZƕqW H(h+iIƣӧs9Q JL<‹̹"* yWikm!$Ohqf>{&L8??$ADw|苲Ƿ#?ĻS<~[/ 'D"Lu",4αsMwkAaܨ "8s3⧥ g=sxbVywpK΂ z hFv on`r%gpA #.VUr߇] %FP` 6yjgON7G0L -46/ˌ."J IhYSP &bF.Ȩ!ȷX?c1ӸHs3ZK Y3>GUQ (A#dAcaDD?עt @EI[?Ᲊ7<0~L9a\<>6l/⒲y|~zoV4Yޑ#W + `9G JⱇLȊ&kÇB{<#O4br09҇4DvЭߛh',B$ fJ> 8c}<ڏj2ѾnZq8]1?r03Ͽ%_O*Oj1#OX6-|ѦKȊyiٳ{d'Wjfׁ1SFѻ˭ٽ=֐CiOlǟ`7{,Y˾ /nV S5/}DO~L/[/ξ m|bH01kH?SeOSx;g?~TշֱY1G쿃g[Ɓ@x , I6os,e?4peȿ'4p>2ճORw&ZwK }]|x7m|D#!Y`Nyuu-^#/[ dsOЧE>/j$~(;NG.5 z(6=r<WB_<ԹXS5;Rk޷r͇R_gL?/z^M{/&eV4h'o_6O7`WV]?VYó//<Î8Cߣc}Ԭq+_Ca'LQ(**bMQSSkZxqF ;4[MD v÷165R{&ް~EE$ q}tݾe۷|c[ٲqa]bGid.--W^崱u^{UQSXXRwYdE]HEYdq`umaw`8KN6|g8VN݁颵f3W_q^8 )"I%%s\ 78.V\ENN%%% )c@q̯*9rLμI:N p]s;e&']$30*pӘuxM@gcm e゚۟暓es`%Ή֦Z`  7POcϠOpbt5Fd ?3qǨիi7d"ݝ{R&P~Lpd\?G1^/a3v,j/㴻oa_1#_ϠLrμWZNv6F\M 6(崻ogbָq9 @ˍ?̢gҴnmX u揻N%%%8\.VQg:|>N߬\+7⤻Etcs9"cg"{mĠEIqrYdEYtniA:UQ=f :kYdт+>UCP UQE A 4 bE9PNT @w}Ɛ!U >%}y2MnlIMfNeg!"2#f;VRo/kV!da`EUE ͆fcݺuAFM=$ìIQX]绰I<=A^߀?<D;5LOFC* ߁JCpZh?Ј_"]߄__&!A|mn}!'0l`UTH[x5\ws{d~9l Mci;BP@n4 _@EE^@ikz7Q# 6.VQ@S#ѦY\t}2z[-8\6&dnc@h Zm>FA}>$+Ϗbl aI( ݦoM)Py ݂dj ի "b[Pд4炈jETY__ÈO&ɉ),v(X,!B\}s]"'LCVi% DI4cd(aڰZAUC êQ\_Wpʄ*9||zZWh%԰ "bs{^&ȍ|wI vH~b4q޽Z aCGeƌK=CbCQe8Խ_f̘XH,_Qjjf\NÆG}njGdK}{Yfm1zu|~FE=L"D$I[)p3BЇ"Dʈ/Ȭ*hzTbC;kѹP=,⨄( t+-eՔ'@~AAv ,"#ζhdr擨рbSF#7/RjNR"C@tHHeE%p[6xTQ^^N0 cwH[Îݻ)).fTVVF6-ZsP?5 ^W"*5OkSm-akZ yJcPaY_!@BZo6#4TFF!55ʢxp74P|nvv}-Cbsr3i e4_{x} ~O5իQyq } M!SbWd>}/d]6J )4B0D IM53m@\a5WZpO4޶Ė!Ab*Q<b8xඃ8y}](.*bTTT!YdEYt="YdGKx~ﱌ!*vٴ}7Ǖ?=͌uxe&j~~Z[[k4)(Gc H60> ٩D4x!I~yBhB^\D/|'TyM*,HMM vGD󩨨GcH)I|,iljb_M u yWУGLƺD2xGk_GG(GIxcG}}j՝GyE9+F6B$,"3t(BDl4",8Ո Z|\$1gOسg?>W ㈏X,RXXH*>rPbt:f&q3 _s! F0IץXCE ߑh$Z{smyh8#f]#1:1G qDÑ#J6%$fpd,V CGS6ĠT`0բI*;~O,"/4M0k 㠦?B]2en?~ <, F! H8JAB%%4Aт&Jhb 6͎`w3HtOY}ʪ(*,G&odC/LĶbHbxg9 A $>m$AaRj$ډ !+f*ށ3%>2)fC'@rVdNtR2 BL:@y `Y)RTT1.w܁9ga3u˜ CbQdQԡ>ZoXfjQOCۍkG󴃷E !5*AU@4TMU@#ENGq"Ydőjgdzӄ륽=AYdUe9P?x> 0:bZdʌaʌՎjs37VC3*!B\tD1>L)oNĞ19eGHTIzb$4e$V0J5#5#Hb$2Sm%qq! }#tܪ8IdD~כ* $]5)&[2 Aw&Y82Ĥ>s)ȫ$uw| .b1GH$NQNDNK-;e&,8iv#g;M455QQQIInlΪ".}?\"DN,VD}G AD! GT*YQ% FP({Tkgv)ʇO3E24Y9)3Pי #`$ iA3R3\PH$"14"RI79-vT"^-yw&rZ|SC;)/@KG͇&Cͩ$Y^dHL q*Dw{)ys9BdHnG@E6 I!*2Dd$MEBC49!5DGHb IDAT:HYdq#d@={.Kgf9EYd+g㔙04Pd!(C Nta/DH3%$a.oȉJt%+!6/L 61;Щ`g-1rC3!/< +; &&d,̉w:E$H&OcL^$XcLO;+$9|]p 2'B C$7<;S"$lHDt 1 Yh" " "8IM vE",W;xSz4 BD,Ϊ",@%}Ą7z312Gi?b!O*%G߫ r@/'217Ʃ:%Ù#M:HW:R-pڥe R&5ɑ rH$!ߴd$y^S'6"  HClYdEj!I_KU",p WȌǓitIH"iHޤ!aCęOǤ1$#191#1H(+a'Ch!D13DaLSʇNfF>khIR.T$θ)b.!UhdH",8%D8yYdEZhRӷ#]#!$T,li7[RNIN$&UZT4!E3T%ވ2\1:>&HCe̜x%f*;#m0х:ޫJda".&:'g3$IKEb$7DGBTFi 'VuP#FG; Qߘ,"1 l g#D",%4Z I?sHlT:S7ߪWωP f*HLt cT'e23?I ^#$GbY?] h$l:]!A"$AB'|d",qXvXny]Mo[#|soS=ͻymRg_}5nFKҊz & ǚj!OMaN}%߬ڈ_`;hgK?cæl+M̰=Q|6(r2"0ciscAkuetOiy >Ϻ-;5q'ҫ e}(Mx J9nL"PX[o&k^e=oʺwG=lٲ}D,ZDp4lX"F"F2 MԈLdъhr S!A{GD69(Z>A4KGdP6rZ鈋LR=#B"%B2+]+$, ?G*RP>y{=&L%lm$]7Ѽϩpӱ@o~DI\xh>xuAGzg FOΥǝBqA.A_mŋxm7Qm؜%[C[jڽ;Y2>׹켙ɜ,* q ǍdKy7 )M:&#y۷~n~zhuXӦ0mfV7[me͊UL]q*lt4Z:0F>HVhtb0eF3FDrĘJɏAC_$%H 'fr1i"IbdMk$>!$'ɊgqHb¡=Ǥ# Ҕ͸LɏԦe/yE2H.&AHgFZDC ","!Y#q kT6~ RO.4^ɱ:1c-zy9x4OyTYʕ7^褏؁m:[μ} ,/-d 9K>* ؜t/B&*l b<=ө5; ;.W<0 ;e>f g;WYd穧p;$Zl{8ܳ(/ABA ݩE2|3qE̢? e$1bZ.^s 7HL葘)6 r1q$:1vdćyfhB˘=H'MJCDyIr)mIGΑ IjIGfERV>QDHGt82tAuA_>Nb*sIdCs՗Rjmpy| Y).)OA8%p-^XlRbXs|m WumsOV~uEZ>>'Ϲ>)Ssyˇk+//洟C7nϿۭ4#s UՏ1'0_/j_zE*7i:S*G<[U|]9_._M]n7cGg抯/>khm£ s/eL򘼀??Bv4K`ӶxA$2m *H($ەВ< ]aނ= 䘙'1ed_}dǃw&Sl4[Ţ|zQ9Y:ocF.+.ai˸ pX~5Mf>~ d`Y@lM| /\Īp{DY*&M;a;3Em,`q'rX"?u;?owԠ6 Mg?1Bc'y^>4MfTCea.j\ HTGPDNlrF:42#9Lrü\r6$6"j8جX$x$TDIBHh$}:&R)9qy}'>g*<Ӕ~0; AHmJ)PbT5KӁ:6o\ =Y3s;݂(ݿuSֽ$!i7[8lƪa}$pU0+Vojָ$H;ׯR9Eyk*v`զݜ8nPL GơWٵ+ޚ'QogwS^:C!5>{eY4˝f,"Œyp;E `cu-+`m =ݻ<6B Ey0>3r4.? s𵷰ujYF<'A([ni*Gؿ}/%`dvnYobp7$2'_ߤs§w?O#oF>|k N=9 9~.)R&J{݌гU],C)rںD cCs~_\{#9~/5zjzNY60\8=|QhkϿÀ)?W&lY]W:ә -3[ۊR<`G}n>]1:ߠ^j< ]E߻Í#L 'Fb |G2GR:KDĵ#QrpqkL)J9&bF%Ĉ%q:DxķEHIaN]"I]:O~Ğ˄$L҄4dX@;rՏ^=z3/>?b*-aKY5#&z6XCQCdG,T6Rw?2aͫh: 7v3sL5N+ZX8 ߬mD_rm]g s%Я,I`0xDϖż;G6 jZQ@~s#\x+#镽q9z:z;F϶pTV1t0vC6 iZoWoi^}`~/QUfn~ c΢o<|+1gi.pD U ͧ03nXS8{~ÄHul2ϣ 2}"O<;碩xbL3l[Pia\sT,aM 1mG~/x됃m3I 9s7x4@ӂ|9Ug!?27a*ʪS>G0}l91X$^Y!4U#IJ؜Ja-ki#sϟqhJ6J^N!M :H|m03N DAbzb/D | }!AW'r)'IIJ:MCg^$):dG QL˘1#ӥ4L5$:d27!?ޢA̜ϾXˈe\LȱTPU}ikc6NڞtFvU<N4vϾFL$>@o~8RNU{ǨŇ+h)vϭz9}vS^EW_VP:|2#Dlı7싵 9fNNJQBD 1Ȍ=?ͧ0'[b|:ۼtI*" Wd\O#94|U5stQTN}X[7G˘s֌xb&<(@0e߮||psQ1H9*|Y)7 7Sm2azlNaEOX^vԴ3yj02ރQH؉ ΂`*~z1qoWGsj'OF sMD#&'{d51Q7nd`"Iq,Dq68⦺Rۖ1;QGuddK䇉lW 1II$LHC%AҗMߜX!͞@ |@bCrFgʭ>!A&$&tdtI]\Ñ[aN`a1x 4IĊx4$1VYpbь`0ljvm\<0ZQj0_%Up.|uͦkJVBAG$:ZYSF3'D9@ĥ ~{vlsL 6ʫzg|Ν{!I@#[2[52*)meN3Q0ؓ{wnUĉX`\DX9o+ pZ,4`/ יCyi`xn kv1xI)"2& nJIG^Bft&QBD%Vv? = RFg7̞3'f_[3ޠ|vijx{Lq &"2A%~CЧG.7ftNlDNa)G@.<2D N+}7R==BR9Wc9@zɺ/j@\pB^^ \}!~V6 <3I|  [d!=u`UAG]{нwmNĈO XQ#!4Q#eӓ#R&HLQI$N[$3lL!%h蒟x%w>f6Aj%Dz3 :B~wI))B03'|r,>>t4r6[vw2{m]7}K dԨټ5D.3.eTdg|l=#BSyBo{ERp5 _䅞,ƌLʑEN )%evFGA K$P*{L0QR,{Qeo/]@$aX셌ӓZ^!A_TW,O|@e XaX|h41y?uP]7+ []KeqH}ve xeoUe@SJf-8dͥ{j\QXcZ^)yvDd9Q4dUCa[Kg8]I{6SlzHH%R/K.w\%NHBBB1zް-Yٲ$KllZHޝyffwg1_!=-iL@֒,@|ݸZ] !o6Pj"bopV^YE۹hhÞiThbt!jtGGŔ#>R#gI t9~˾hl7T̡TVA"] kvwV7 bCDI%%?4 hhۭ|x"*OAu_kFF#{^i#](ԩWD[AJs\Xi0Bxh>;{J})<3 .waM56W8wK-!]d ȗg^iD! 2Eߵ߷fъ"fC~XNIIN$8[#GE4_.ewJA'X>sQDzm`_>l+лkGD<-G뻞͕HL_ KG/سy NUqv~1 ?s.'6mAm߽%k$GR\G.ЧUw9Ql\zyG8wcZ_-dPgԤ[3TGz: $mq9lHb_W+so뿸H9q@*\Xˌs; 6G(s\4Y0i|ԟuzŘ&`[1'X"ewrѶ}=KnSz nR:!H0ф|MAB/ A.fȜ:X5&{zJ#&f~t ד>r2.<@ ܿhѕ-߬|F vg1o{ᚍlߕc8mo1G7l ؉rQ,n %&ny>ҁ_ %޷kqUO=5*3t {/bW/]jNQQQQQZ∝|u z޷D?6j|gϠ7|r{Ly㽹?^ )PV {Y2=>^He'ƿ]=DxYWmRqsA,T3dQu#7#M|Ή ڰ ¬Za1*IO^$i9g=3V+b$HظcnWbS:?DS\ɠ8tNmKm5@ct2*fDF6e6t|M bCn_C^n:wA *W&S\;84lz]ADQ@Ձ "+u "# #" "At!"yayaxxrskyOƃ=ܙ(ԱS&"[<#~ji>䡧6sLpro=7!o{?#?1d鬗|t|+kVHƓcڀV'sPm/=fdsy&_W/i2c,"{SVIr@%1'Ƣ*m qxKxO9Ч/?ǿG3羿;f?o7wN+$ۉ>2Ýϟd3Orsd(Wy?Pu+fiL%|-VPGT "yVAĜg8)NY8o?=y _wozqgw\+2cu?\<σf#qx2R,z{o%h+LI &NswUyi6OG?Osߦ?kkE_R}x\&U "MEpÔl19wG33e`|Jw?^dmhzZMt{Yxo?oGw\JDXx_}lD2U3ń "#*2"2S"# `DsEFMtFRQi)*ɬ 2QNM¹dX8t`?uHxx;wl'""^!"TTTTTTv=/me퇇N5j(r: YdwC ։{"6}]/du^'ľVgNۇ>OLΌ{t:ۖq`wۡ"'# IOu]|>]zPkUq"=ρR>=AG+߉~EږPo w8m>ȋE5ar|{2a\%=ã:''?6cf&qӧELNGP1/GJT[R`5|(?ŠQJ’2ȾNrud0aHt"sQspp=a c؁Hd@ Va}xɮ&TW>r}\vJVw~/3ʬ[[U?h9]21G bdE2$#W$"?ihB~c.ʵ n5W2#z;^Qc]?V*dI6a#N798k?*血QgC㮙eN۬rP N4qETADEEEE ti8ý3qPGDHog,q~@n"WPPxSs>J)^7KB̸}N+ؐ+/8Β;o6o _{wK+ӥ]Ϋ^jS[E"49:o4JE)K :JBQB{11u&sVf10 ;Ht=K kWJ9%UzB%[DP%T '6DEr_zyRʦWݒq<vu::%Wۓ6s{Ar|E]OkRUamTdž7 c gy,d[G+#9d4^H#&Eߧ# ݨz>8WEkqakMMADTTTTTGhHW]ڔ1g[. t䉙oscXYGY#޻A5%)NQdo?uh7!!ywڷv_q;˜>LkUs) :* ΃`P)ڑfdlaOYQoŢQ$\u<3T|lDu 3PXnG˅ ^xzUW"]i.e2D?¼9_b&rluagw0\쎫M7snS)^(w,[ϡM7_(+ǐs Z*|E2sieJH'|3pFrcѪ2|bTTTTT.?4bHl6g6|p*f(esT-OOR.+7cv@YB+ v _3{"BIv̬OOR+6V(0%42*92k*⇺8Q@''1-j},I@R>+a@BA/듵:){L+`v(L KA?%ekҙZ؎JM};dIla]0E}xquM<7NϠYd|Du9Z!A+/'HAt,K1rj"U::]J+މyT1@\{jz`EC|ʥ^ HN71t>ΕTb6pn2>z.h%Vb-َDRߘt?yf߲w*%1$,#EFxu16$e~Wc9u^c[z\Kv,<߿̫ktcmgGAcYu?yf߼} 7Ա۝ČKTmF h~ÉSNAE ޜ{|Yx7S"h+&xmK 1PGlEg9VTKxVx%3Y>`)ʳ~D{l{!$Bv~䷏xaf6}#Xr8# b~,ܓKLE!V}oGaá9 F Wi\Y- y7V%*\)3*****د۔)64AO^" y&+y[Qn\.RWJz;M%ZbtP?zE/*MMR$Nbo_j,&Θ;7@C;g_yW[0yj*^?@n}vӳy糧Ů~4*E]{a o5>lKqZy?|Ke'LNjyohX*|\NL|>y F٪ [jgr+[a>ek8y.DvbaD]4l1{z6lINa)abPtF!gewcT'(rVgKo*%ȲcrBnn][g y9tܥκ** G叇`tFADV6ADF@FDD, Y"K,& Ƌ"/7Rڔ*Xm>M׈]_n[i.3Řb׫L=||7ڋ0tUidٞm*\)HDhx$XslD2U3ń "#*2"2S"# `DHEFXQ3bbc,: 9Ƿk9|R Nɺͧ{x|n}YQlZNV1jP"2Nz2 !L? {U8\ "\Cr(Gsv"""(ƪzda,;W`]t7e#)}.mޞk*O3/.x(2gqf:JI82o/Llڸm;c_g4- %ygYf{DBb{c8,\>3)r%E3jW+KŧrLǫegm)1-wsC#IKI]k.j23烯)Ah:4:= o#'%O琧~guFA döBRצ% kxE&bpkW6ڏIFxBbNm_[M]J[WJ֤9s6Qf*~*;[3qMȖJF3{;_v}:ʨ.6Z*`.*Ķ=:-7N0{N&E Tx.9ˁbv(N `ʌ‰9}O2G+by)ŊF6W%vinvKݮ=w EDXDc\K IDAT~hDgd(K MJֻw[үwMH&N=U휣f+uޡ=D.k(Nre᫓uo$BM8&Ђ7LYo?k'Kl{^4$6F x4˹HBVQ8wx+?>Ƹ'Y}9_daxRӵB <5?سzDUeWƎ)0q ۟nXb{ʌ_p$wv.[Տ;rk_HT/V0[+,R~ Dtl(V/.x&_EEʤ0ݼ ]yêhdo\Aǭ=3g>iOwLߦn(A[=<X$,\wO'%2֦_]Z^VyG2.1A@%M*B| *w*26ä|i'ha6}Sz qI?IVR4[,Sz4F~(=̽x$E.)ؾn:ID'T]N k::06؋ٳշ0*R 4rB_VH.ڃt1Vj7a|?̣ymnB?((OKw1 ą8FpW^Oy /y(HdXIcܱ1DzS^t/g#Ko1@o-yNm %D&{N_x[# =Gֵ-}#˜։hRtbn;w˗ҕ`/4h`:IWQQ2iaADƑAo}<ڵ0̩XyȊ@pDw6 EY\ɮG0 PP^ev^,:1m2btyk.bg.0[cѴkQ!n-^Ήs|;l4tvYY%Iw߆^s|(Z-(fvDLF_؉!CӨ<<9 ؼM;c0NOA,Y' z{d&4AVoeI5糾h),۴n=IC%= :{8#\>dP(T~1UyzmSiQS$qgfֵz`:Th~I\|f\ 1o&4$5IKTġgu+>F~I &Yl*tQcz^hU#]ubf熡mQ)Oq[?QsDVn;@϶qhER=g;7v):V1q`ZwIWˠo|`3, wQ,]V_{ű]WmkDdmg>:_jv&}AXW?f1ѱ :ûi\, 'ĦOON$GKnIlGAD"O#g?D0vhwNŒ>vLCykںD^4spF~Knkc괰0k/_~05 XЮmhT~]׏%&ЗʆHSZ+)]x gK=LJgb_Fk8'e#3Utyt,g 3} 7O%Mx)ņ~s=$T.n( Ȱ];d.gUfL`Sضz#c`}1)xc_AN4""WyWoڪʜ:JbSt4-A( o`Ues}@+u^rT2Tzwi0@P;o!uG%3zt/E %4ލ9+3㻐&v ݁#z9-UUZl9uuj2mt`2JP6@_g%<X4&Cb2V'AZAbhp<AKnQTe8t4]cȤtܬgŊ5p.>t {D5\7q,~Yo?ظVOL$ E6QY~U_}رqt>"ڵ#'"ٵ(9{h/^m RUg:&=O%ʤa(Uz_h***W "sT{6uto5^Awaӆ=8)0 @tr[me*\-)61fc\Άm{(Pi.ITYz'y5Z9+" R1mCb1] z wh_Pt<…ݔx;p™ؑ%1 NyGԋ/b dY#uR,q?T͍թC9 35Ӕ *9wK-fʭ9aGO7hhRדbJsP FZmei5C< (d®s"OMo[ΌVp+rۘ o.=4TUd9F\)c_z&r sU %<;̆=^TW*z4ÅC1`Tܛ5fGb3qd /gN%ؓԛGx* 벤皺tH7?[{{0AUfey#! &DEDtx`[!*f Y믢r%P464Ju*G^<+KαF&a9ULVQwBʗ-M u^>*̲,g}b CTqYo}Wo;<+HZJV`6`r]dLii!_Z<YEXn&Ы1#tz?ڦ"$P˧_ohdO›Y$/"Ee;L OIH̤[`)CX3LD-~.(Ҳ":D),{S"V: jLbk6 m ϩ(i׋U_=G`jyFMN4Q6SbI+E1[@{V x(@3 vI`h.][/7 ?,",4:X zvYN#<:ZG)1TGb{]THĤ&nbT# !>ukɓjEZ9*+8I^8F4/nW3څ5NݘiWέ9n:lvV3ZD /@yj2VC# HY\2Wrx)NuXpJޖxW\*&C9 ; j ?A& [u$.Q1[fF:֏CN%>YoAh,,ͷꂠ?JrX7f4( \W fL:ĵ`$±=)3\0&{OA)V8}p?X:0ZhHԳD]ԃ= ZY hHۃs29H4<oZ-#>R#gI ty8!w۾#hl+J 0K?"WT@x2MZ`4rrLQQSY9?5@gTFU>b >1>httn|C֫trx3_6lĪ_c4h~t)|[mJ>xk9( BH^" ۜMZ8OąkvEX&VXo닣#߳uyXEAZr?EswQGr%VZO1%;E%HwlNp.~e+߆^#! 1t0{>B] ёu͛ф'snJNalktK@xh2޷CUÆ1W8wK-!Ϳ=%}`or)kuнS :Bl~>׏"ۣ45l(h)֭pSG"smr1zy|i%aHLYq!GN4l-&cůˉM@P ӋE4%ű1|̨4WTk+eꌊJsՇSO[/\-T( Iiy0{ÂHtb&$vhA10nhB}CxɇlG[-ACnh׭_v=M d(Gj=!riAdˆt{5DQO>i|t=tcE *S& ,\̯s PXDZ/$B|W*MC7t>zDQ`T_ț/0> I=;278L1i(/X#xh{FBT| 71.ds~e95fE <.AÆ5^!eW$VS6R_B4544%D΃1Y9/Be LBR =h?ŏ2r]2#E~#ث%ٖ)kXl%[4a1?x-F'iW0}zv`Æ ,FnCbt4544%DRsOtm૏WrZ &u2nL7cSW [ַ0W\x:TI])?"p̕WYC$ UPQQifdKwDNRQQbx )΀b!77n术\:wApSKwʵ mg-( h:DdEnDddDdAʌ,iht$/,:/,/9瑛Fe, &R:ZI[`h I5gF*#Z\h1#ZLHX@ )2"#"c1U!(2 Oq"*a2bbhrrMbX8x`?ᮽEQ$"2۷YGEjJAq_EEEE0LDL\dBU߲\h%yﴞxfCyy!sl6{R%K ~W@ "` D EQDS HZA :/}@]TUSfTA1ѐy`QJKQ_*+X *2"(& bFPdlE(R `(vUr\Ah4QVVsrvYbh:taJEEEY U ! " #! "(!dABhQtzd7EVtLuՏ,ߴF@;42|FuD'cJ|-3&eo3bQPJV^^zAA@ :-ل`2"*fٌ([AXEEF楨BxRTTTTTUEEEADҗyrVƳSIA.;ϑ- ^q_] W4NfQHDX\{TliaT[$+V{h1cg@jgDy ټmli.9]:Kҽtl ڱtJiߕR{w*****W/vH>%Wgؿr9vnZbtl7Apm7<511vxno`'g ]ֳ$n^O{zXߎ< c*/| ߼8.EAYZS?-Q^C6/uBV//\s-`OV! p,/K>w t+SwJ>#B U7;%<6 Kd_#e[G& ٛ',o4H9Wf2nfޙAER?$]v;oىO<4@~ Gy[g!kxz1 8/wͳ݆#+ˇ. q WaxKޙ55 !5.Fͯ>}!%">{yç+v4s^s#ZQKM!;M SPyVZqP}F_9BnɵWqƪLB"3F9,eB)p<\,׾cZt}wp|@~'o^ێtͺ_n.kiF;G5X*'j0|8o[.:}!J#6w iN߈ ršBOE-֧n"=v-fI۔so_D14<ڦQĖV`:~:XX nӝJ)֟/żU]h"{:-}(YߟCq/䱄 wI/,/Wy'㙿xmfT>njf`+՜A Y"b&O)13dfNuU!?(~(wd;ijޡvU Lf44+ՎjEEFţĢEO/F/݁}K-{uzy/3LˊIju:& :vL KvGe`d )05˭ڛ|OE/,:V-ۺ$6.e$vIQ8;Oij:e5 7sx䅴={rdΝs#r6wŤЮ߹OIx23Sm%%s3U$ދfH#c'ٙNUZM^THcٜu\ l+H͠Kd'i]AB}_ E |uǃs"8pWG;u"g1(d(9a| ߃R ]&Mx`c,@ 0o_uutW\g|!\{&0LHA-œϺO~<-mWsh(/~:p{xR'xfٛ,0jf:Ty$.Ꮋu8zqza<"h M Ck:@qfծx_FV/=ď+6srRg=? Jm/:1 %Ѥ ?Qp"[yե|<ܼPǃQȖIi&_8 & ՙ.&\Cޓ驘6`gLxDF0b5ʏGEQQrQ vE YΟy`47Z:%c5{zk8Qil>l/e#^5ZT"vg!/!VꗫX!א93_7$b!m W(%iObN&ZӔiOX?Ūx ~?_d4(HRrsJtiDߵhCH;fkf$h< 547Ӆra.tn 1 [C3SCJY;y!"(ӅHпB.o3`Whz\q.尧U߇ S ֚Amb =?%xJP{rpki+e7_sRn ,y &JR&w?i-(GXշ~hW 9~p{vlbgyAMHh-/1B} sc$=~+q&OYs0"^iUlĚ1qQjŪai:%N-SM̸9\;ʖ]cEEUӦeSTO Q0!/mΑ?buh筍SW1؈Oh |1M7/#^{87[8^.f9YSDo/ji[vpwggGw~liG [yPEE5#Y.pEICj'C suMʐ rWe۴`P|.?;dt\EN.ӷZy~$ۋ$fH9Os/<y~DJ7ҬZhwKЏ(Y֍KU ]$PyK-\;|{U^?? e|v;@ѿm.?7Vdni]ɛ?;7'|x2jzX蛞͊ '2ʺw@q adZF^Fҭa_kfrui ;!g DT\!a쟅V<[/\DҶy˘4a&L_mi?ʻ<$_kl"ytvdZ_y--=¹|3҅GSUK#p^zfy'1oBōd=l`ov1.gA,IlLe ^|-]E#_l, nsO>Zʞ|'l7;I H(5^شYY?dwi{/p>!^L9g\ k|ogS)Oo ,LsQY%g5j~8x+64fYGץcVqg󂷘y@>Mٷ5K$USmH5>P],qKdCw;/@JMo1<lM^keҥ=Ul$H5}#RL$ċ/&ӷ&Ϩİ.'{-$gc~ϨVCwt H14ءUmwB!bySJltq_4rsNNVqѣGaR@:UB4rԩVSU3NUhE&%ǻcGu=XLr\l۶\\khJ|53233իF#'+<SCxvhiUU4AQ'HB3Qqg6_hFf\=o)nG>|tGpNTUp}8x`tUhּ9kV&%)U( !Dcaefq5oV͖ϭxWQ,$%tԝEMk QwB!TR "ɨK>kQVǠRGdFŗNhϮ28ipū3؄FS]77򴪪/.up<@ ; jΒ'*]C՚tBqΒ۔B Ralb)j7Df3U8c׫j5*zcB:RIJz@$N ´4 ÇrcV!8.0Bt4ɌB3=: Bqiס#[7oBq1Q\!\IeD>B4V9'AQhފm[7$)lE4BQ[(!B!IA^.q7 B0@D!Bzd%X!]`;#TBpi!B!Be­, Bxx<t]r7tTBa6NkX]!#TLxHBD>yU*/z]˅fЪMkO?B!j]qQ!tVh=C!D\zQfsxPIa~l( !"*:3ټi#nVW!=J !D#On=(ϣ #"‚|E!Ukl@B0@D!1]%&6Jmi!h h2\4$"oHlyBɥr:=JJJdhF!8x|nA ( !"[(B!B!~u@D!B!:&U J;vBjZZ!D< !DUUFŇ`#C:UB!'Oyfn7TAa(".nw(]Mk׮$&&6A!!8cH_\~ٰNB;/YO^=R!dR "B4Rw{kמ膎(.*¤i޵}z7ttB_H$:ʯBQ`[|X +&_UQhժ5qq !Ol\[}ɕڭ!7!3BԖ:F_()  [""S+ &.u!htvaя/iM5P'ÏEAQt>9Z~?1WYLz j)J'wy)6-_r uB!2*װSx-hyB!}0C.79 %k3QWsm.ޜWȥ\v-Sd29wp[wdݢ],f^6;gB!D W^C !m=/k'l)@fq$Fר1pzuj?g9Aĝݹun}r?WWnHlfi=_lo8Mc!ԨRC!jK^O-m&3-ySeUSiLic^&]7#G1r8.p_5qps F.|v.3rpTޘUu0j(F]p3Aq, ?7|k/ȑw<={~(tJ 1e(FN߾;J mkO5*o7?tg:5+3}冱=ޒ6{-GMo>SyCxђ6q_Ff>3ZҦUO.xcVޘ^HoIFs[oJzP\G!-U/shuz! şӒ5Ws8go=bs==6^ɮ_.`ެo?^X"dI_cÛsX\{\}8l٢s}QhS ]N(gjas8^}9oI3ya߿vwϏ[c }gcKNO?—>ǎ!2wn6~?kWcOmrn6-{s6?-n-/|+^W6濛;Yopz .۵1;=֐v.dWYa8f#woa~n~_yj"B~]%HB)<+Ϙ7ϱcgzxIFv fdK㨛y~|GJ*4 Jpl=vB'f ;P,vЙd{s,cs?a ~; + {8~~fIfhZ/sp}Szbҝ)k./kG!qL<mvAny:,Eb+ H<8;1f1>a^8/ñ镴d?F>cYɩw|9͢-DO^DxNxGXɣtFR+xyk)6 .$E;y"9쌱'vE !8IBlL YG~|g1L%8p*#&Ŧ&Fzd 5ڢXuzrNoac?Ibfa0)ֆǷ=v[UE0rc5jFbfcQ ʪuҗ/1,:\&֐W_7[sYƗ^s@nkyRSHۿ4K Eu-֪iV?f9Jwh[SVicI;4ԙ˧u7Q!(Z簣B!YR_Oh1n:m?8~fmhߵ~6sI2UY݄(G%5 k$GVL6!kW̿z,o'-تQ,QR`x-o2>w=Zg7/+?5DOSoӛ͑Lo+;s7 U6 IDATwO9;)AJTIk>$pŎ|.mzYY[dd Cꍓ9vl=Ѝf6s2ML h丰Ecv{[$ !R{ 9SB38mKaPEn컋NRbǮ_ i.JDwi?FA`PDԅ{_+ՍR뒹իZnw~rh&(n Ee{[,M7$,JlIDib!좰Ԁ؀{܀ȟPӽ!97O9WomݻǾ=]G)4lP6qذ#_!j>e0 !0NUݺa!^6Lِ5Λ[|K}ٸssrb5C7 ox) X8 R.]*n52'pL[WmspkyW®_sI:C~<ՇW'*؇9 ~¿C_r9d6iMGAn+ QʎO>`k!kޞ1GbQ˥*2`}(h֢ 3ÿ\ubj=|!g܅p||S_cBT9pzpӅB4&xX&T#@KbU8x''JܸKٹM0JRt]Ђ/ė(|wg*P|JzWlx$ݫG_d~cd;s>;r-hQ/Z=SaY!Otnjy%oҶ抙Z cþ'o_nX W16},=wN %p3zmkC|0EnG3[xhKF?(j2cJ|O|#E.\EYl].O ?b~4K'sˡ|n'7wqѥ#3S&5B!:NW\y: !DJy}@+*n(* :?MHi֜'KBٷwߪֲ%VMS45`JbRJ?TI(R 㟢("(*`z_!GQfƣd0[0,6<( Rį@DFFw7l4EE(Tg1pc.Tjx{t_A H(+|prHy?WBq 2#gѣDܢEqLD]*-)f7!aX!DC!8CIAnsxqq +Dc:dzR!R "h5X|`V7_ݚ4I$)v$n!vSRRLv(GpÑB. iZ|!8ST-\({ū1 )nGQrs(*,h!M= C>"{RMtO*-U]/LaZfRCD!௒bf5t4B]:-gZ))))\.Q Y]彡ˮI~(CQET PQ4  |fP5TTՎbأ@vd8\!Oq`81.N< $0<(ѽztE{x˲h􆾟WyDJJ v{}E /;okxW'daTNii)'NfwBZU8.Fٰb&fB,V0[jkXm`F8TPnuB4.emu̯nۀjTL Xഠ. ݅Q ^6.bg䰻NǏ |_a.i]C$//1ͧ9!]a D"e!-QUoAQT< U5QLf+;j0Bza6Q GGUtlB1kǍQ}5B"JYA]3@rQTTDn^.Ԑ!.0y/raL|* QQX,ӊBԉ"<c((eo凜}.[ ҶBTY ϕJ:|:ߩUl Y,0 e`B+B]"=+.PO72f !B!lև|_ɹvj!.B! +gN !DC_Ӯ!"B!'R"[C$T7"eEY|=w&E|tebnO)iuQ[ ŞYa+9(itڍh"/sY_vWD.)ze5y ӵ.d{8pv+ɜnق10h:=Tywyw0pJΝILHhF ;p^r,8-0FP5!"_B dՄviQlqf:Ȟh-IjX`{xJ)d4K$?_~^pq\pIٱev98~ ~ZM~}_/8qtABc%(ƍѽ !A DE#s-L~ lU{|]l~!#0)Ҳ'R%X_]U]7|އ&sb*CGO5̖D.>NpR0m4OoH7o[[, _%C3g@ OCGFMi߾QѡkR !_qQIcիp:0@qT rCV榪ndc7_8yxŖ&JCyDz0PMHk>tiV#osΔhE-`*O3dnXǦqm21,8Xj%Qiա "lݬXytݟ>]2ДS;EQL(?E >to>vغg%NTъ'***59]eahceʞQLf4SqTQV\Z{:^ e_V“8Rh}zS vҺg_obB3[Hdl߸ 3j,XS33ń=&>sd:0=n<l!Kowhd"I -;qS?~ߺٟ|?b]d&_+^wO8gPb,zͣ>,~5;v$b(nݶ⸖7=^}{.q>w,:j6aСs&Jnxt Sut Eumx.{~9z݅SErZi8|=_Ƹ߃Uku !D}|k2g«‚Ȟ3A|%/yދ1 }L ¨Vi(:ulƺ-{h, `nnIv툵 8{䀵[CM)v3NOf'EML8:vɱz48&1tN7V{վQ"aژv磏Xh1GfѢpm6*Un_5s SشimSbR0 ށ؂Ը(p壚m vpo^GE樳 Ty$ .cqq?boΈQCI 3[anMwIQ}SngAys UU}cl'=ܶFšKpuf<o)u+P-VtA`U5["3.h9_)?!'ͦHԪ_;MBKdrц{_k@_!sU!D%F{ [LQHm6/X~'bpdבRS7v-(FUU ?UCUO)..`ќO&Ka9$&$py1w\>3N'> Y[u+V DPZ*bt8TńZjCp{ 6P}噢iކfmlZ?aġODrM>M+F㯲>LX.<`?ˎ &ⴟι}*Y7x hf3nw1?"  `EޕUL-r[-̕t+uf4E aƧCOR] ,zCfcRJ] !Pۆ. =HB,DڧDzuA;c@Aâ)8\.xRLUeP4MZ,1,T44y4i U;8=]Ed͠_^aCF îWXXX!>&Kf4߉GP&⋼pSpBt;K1T3&U ytѝ_&>C,^1-perݿt[i}WF&B-ZSg=~x߶{g3wEE|ukؗcnґmvtFw)e8wj !į@aa!&\q.O`@dgm :unS^ȡl$ǙSAαC`#^UgS:Y:]\NWM6R,;x8,g9pM7ѯoj CoAv*A*FFܻvlK+ݨ<v=IAցhDYLe:tgWo4Ht`&orgwiѽ?6^* ,izGKٲJ^scGcc* Pgx=o9_1S!"_{?>9t.fM[y ''Mˎv0R <VW>{swyŠOKcsyi\}:)b1 0Ӄ'>[ X XMMǿ?f޼y>sS -ptf.E˹D!be(fs}>!L롎wqJV[2-T 3(&wmǾuٸsNnC{lw9hƦdNiQ>׭d1і/IVT3{vƟYmOye8Y80O*se-VoAZJU-X =K_t|sZ]yбMߗydub7,e϶!zĬPyf:{6Oy>Kn'%+IlAտ]E?у~[P_yTT$TFٻq3NNӮ&ټqtCx)V"z O=;)v Q)ݘ|$c;MA3f|>̑UpedvJ/Go~G'ndOy즦,p'JJs}{aZĘ14E|j"-kQؖq7\3M1H6/O.(hc 7 Bj=XEqfkuLmټ`%FRCQHjё1V]$6`]p֡tK]Q)Nn3= XZ+Il*V̬1 >={&!H? f{L,_(J4S_@r Z%/E$oRbǀܴZK7HLIcѤ'W 1Y_y ug&Kc'fmX(&6C^tl(Toѝd|;-^{g&-^J=^c?l˜[mUٰqd|"thzJc陨/<Ț) #Hg8ǓamÄ-0-g"1$~}AtHSд$L[[ KHcNV.DtbKM .oMȰB4~rIii|!Q@RZ'S*m2&3&L^>*&2&K*V,g['ԶBNW31-d:Bj.\ڦKܜx{3[`Foh YJbjKdH-\su>K'yWF쌘|aҮ9/BE{\5Yt$ Ͷx I_E!x4G1alƣh۱-QT2 Xʃw>wznJ̈́Z›v;bz16%yJwH &ybH۔5E:oMbH 0bǤ2,B!YU>*:6r_=۶aoA"ITF)oa3:X#(lBw1<FjՌ<FNK7/_ΰO<|hv,%wcDyY#FbgIFl&_6b-dfIJm]ȱ gQlmBb`UA IDATƣrZ }-W~m)*!Bg!DaŃl6GU㻧U܇{Qvt&X{E*(X7i%O"Ys쉭!"|ng}[;ԿK@gBSj&t]Ǩ ]ŤV[op[ƚ|n8[:$s*]ffkik&{5EW^.;+ C?." ҭ*&ENkM$tjwA&wN6L=?!Dc=xZ㨈-"4_Hu^;_Lx.7\u\0q QU;2Y8$F:!HQD,۵r^P} ~P B '3IHMݘl:sfdsf7p GbH{Pq7"Dz8SB o9{YËO|Ÿ[J7EWMbGyz3 s߼J$1pN5k6n0#X-<5O6/YOru(Ru݅/ßc9_:V Y vJakc{O'pg]t.B.`j-"~)ǩ~7ʱ7mbQZ [s2t](RD.V]i}Q5#h8n5L L6L0,l,ˋcy=17/@DZߧoE]t֭5з~5רs˲ܵ7}]UV'P(ô \u0] >3 \|q He-ʵжmp:tgkVv6LWD۶ie3stLeZu'.X"D-:e0p #`X``Zkz^o pcq@y :o5PǀDZKyc}k|=F7m =In~dOIxioG~C*~DDڨ_Rq0̖k!JJ_ec p8v;kCyĭ "ncՀH[T " 2U-Bobb{Zfl0`Y*Lphuz6ޕ8X$g>ru~4M?MVF^,_oޏ &4"PaaA.xn0 bf2H*+kn-D~?!"QSsnj>ZEDiV!B;uO/ GϽOㆽO }ʨGű~RRtA6]C\l\5ࠪ-BDBDdo yxBDH#4eͪCDʛ:b.sn$$$QV),gú51-u}& ]o{l9CDDzzy̨bZ$n3i%wf Z8"RE GLLl:0]Wxߤ'>7bk]|*]fܺݕ%&6ظ.>vHzڧ""΍""mDAUMlli̊iwԇ""亮U.3n?T5rjI4mE!""v@ΐ"ۧZMcq"嵶Zm"""Q< nmC\D\qA ׭2^u 4 @V6aYoWi9#PUn|WiLu4H4pv0M"""Pi`;~Dvl vcS""9j!""iwED8&ii *""mUbZngHUDO 5[m vIDD#QDDD]("-CFdTD$<3AUED"GO3!A)"9 D qm@E</ei(HX^/={fOh∈HEE[ߏm∈H=1p C?XvMs)""- !1_x?3}r͎ 9Lb/ctAgp=bc6|oCr_M/\. 4kz1rI@y<&&^>P ǥfs""AUEDDՉK/bGժח 잕{MLE7?.5C:9|g~snMS%kyvc玫*FI\C ;knr W#KM*DED108eՖ-}pܰ}y.]θGϋחN}N327lQj>S-_zO!q޾z ĹH/λR:k 'Ip}I#L59vmuGKs9pǣ/9*rV ۸nzN'bQGDDCe@ѫ5ʵHTUI)dف&?甿_?PvqqK0o]{풊Ym}]. V>o5S|7#F1wSl^wqKW݊֩J H26Ěpv8}ig3|:w\!fo, g\L 鿌vizyI~Gdпp90T[o[9_]eփlz<5w/eoq7<]3ZátlDO ShcVi OcשE2Rs܈'ywJw|Fψ#1H)fsQ7ɪSCql2qlzp q?E~FW'o#y*W5?nƑ#C2"o>1} d+JS2,o~wǨHG+5Pz]mip}`4H4yƀC,w/"1&ůOL߰#qN(O7uy92>m_6]-/y#1v**OfWI`>T6uoOFfe?S8+^ʄ[}MaL*5`o]׼;8d SkfS#BEK2#""sH0 CIq tzm=i>a3_-cs]_=3ITۓ{E(uls0[x`E?zƅzj¨&PrJ-Lyaޯ[קrxl,<8-<F__}u鰛nĄnSˣTDD$:H`} f*ݙG7& 2Hv>^|?l'q578^&GTODiܜMضSmlڔ=`_j-_.vŲBi].KmnobaG0q0p0q 0p,/bcb=\9#V¶dR ,ZV,?#Xv .4M:w;ޟxbYsXexR@XX:8i:gA ƶg[ob6 #)1)l}_i۷,>}IOiZ5tLv ?H""""""""ѠxGˌbkJPED$UU]fDDھmtK@("} _u"""Kۺ. .liCDD>DDD"9S(uܔm߯uDD"FvGˌHР""mi血Hlnjp_Ӡ""m_e=P<%""ͥ.3"""""""qBDD$jhOHP@DD$\7?Ժݞ wz ٦D̈vG-DDD"oΠHjZD$z( """"Ҏ8m.Jx, TCkhHD5vp1:rI(kKMȎgQaegԣ[둊D3JkSߏ76>}Xo~iV\AYi ^r׭:?zs[htmHq]DDDaQFi&SJ`Pz'vqDuS1S[\ջΠH < JK(7rˎ; ◅?a6Oi""CHq w#dmeT/NGN6lޛ$/ 1LY6| FiI1%%cI/%Ÿ.df HKKfHu9@D$t("!ͽumq_JLkG$^m͹$&%5w3F`nQ%%Ť97XUzuoUD$VA< QڮtҒbue_7?RcqLص 1_p/qһ7_pKWko^Rq?FQ{DZ+yՀD]D\uu셼WY<'^ܢh Җ&/_? >=ٓ!{uJA1KW}ȴ0$Yϩ\17L~2'a/샗HYSϽo\-F]o;<}5_}w?/߷e_O8ZD=P@DD$& #n>_oEn}nG} JD$bj_qwx?)g?[^Q]*_49vE: =8O>ocY-|66@֟.y>e++Is֭Vm͐߂.䠮ff:{}6/8o2?&´p]pmt$""OcD$.ޮ2Hf_03y*m6|+!cOsꃵ)&w;Tc ^3U ^- ƼnNS{PR;o13{s~cM ;5;qs8)b.W0C\I4ӥDB(.$o}Hӳq:Vd3f&OMPCK4pµV?a/]WųNC_s\܎T!PՋ=ڡWCliڵA׮]k-[z eѥkm/Vp-?DD$( ""IMQuuCWy&Mɏ}î! nɟ/rm?2yntwX9GNd罞Wz72}VvޣCe¢E0'nň]n{#3N>8l:gC/`XղŏwޮYd՟tY^\u{>qK'-gT?OoL_Ȥ?~gO_E@)`S<g6ے ./^I{^".s< 70/Hٌ7x]OFScQK())᪫f˖\e˖\v2m3 kߏ DPsl01Yy|~*pk//[+Scx' |}v#cϬ)z,(]){p1"#\oβ6cmUI $/㥧?RFsswa5ŏ02c.R߷^:wLDkOڠu%+ y,)z33fY.c-Dj=w*_3}d8'g ;5D_٣;y'֯k#77kus۝wٳGu}jRHZ\#.חNcby|[qPFo[ =l~sVŒoJWE{,9`~=;x#Hq=)pfY>W.E k81tU~s˴wѭSbfC/5r)w+8s 쒱ˋ9gRE%}ƌ=fX7Wڂrؗ{{ȑ!6\%a^n|]I6C/6Ep~KqYp~mu0j~G""RD$JlJkg滇ayI<1=+/`Vy 8o03Fs@y|3zynj2t;1N&N_>篒l{9gd̚j`D lU܁q鋡!VgLHgfWg#}:_ӌu\qj)I\71.[^+6SX PZH_❙,/ݶ^mޞCh ˿{ۧ qaqk3ZS~n.]t!/ :W1HtP q#*A'pVyd@E#Nϛ£s9nt:ǚEsÿybz3~_/^A^̎< ;^/5[E9{e]#͇?&Xzx*/]?ΦCn ݆h.WG-dq0oٸlHzz!ADDD"Ja3sȮiX۷H`vHd*7^>V3<+/;Vղ,z\͍ "R5m5^'/p(ӾʧV2SaʫӸ9r_D}OW/a6}*T2myR99#QsFq>}t]fB"" bFr# BK\[_6^Ea˲HMMee u8]vK}Xn~ ݻ DJS ;}n\uT:Rx\ϛ|(n"4pEf!<6&G=Xf 3?ۑ$:gt{bjFEMHL.""͢l؜߭!iҡC2qqtԉbZBGRR"jU&" Di|qjRbcܒ""H;1DDD꧀H4;x}1ʓ]Uii HzZD$z( ""I-8ˌn%Duv&"͝LDDM(1DD>""C(.3""mi血H4s}}fDD$zTDDDZ}ED"""j"""A->id~"""bH՞76"""HgF0Ҁv )YxP$W`…d_a˪k=f/z剴br6ln ⸎""Q@W""miضKU lޔ7L$­[q~̞ÈQQTPkfn @\֬on:fK qe:/( !)N;t_̙={u,4gS_H>IHJg\ضiP@D$""Hfa`;v0(% xLY7_{AC塴˯v@fw4vuLP6NHjxu4L܊v]Q/6KvN+`m<OL2:v$55_l>>8.4̰zcsfS@DD$Brm[^k.3, |PUGw%ƒARR()),$..x,KmiWz!X.""DDD"mC"T%x< pFyW :)퓋U܍tPDD"E64Mǁ2#!SY% D""mi8/""Q)TUDD6iL©vWA6uLjbH=լzR@DD$P8DD) ""%q~kEDDBxٲ("""1 D@ ҵkw|W Wzԣb6_G eH* ""6 PTO]$ OVVo'cvDD D[%5vϫ(g(ϧR_t֍,&%%vu~c""1 ]D m[OL#""(.*$1)[m#""uP i뺮CIY 4a""vjtk""Ci!D0Ĉ! !DtDDD q_o^׋e"""?Cf'$ԛիW/+ѩ@DDDD? 3c`FM ä_擽q}O߀"""QYKLx["t,"13H~A; JaA> kAA..=2{藅ۇ/߱xS IO84}&UDDښ{19\XE:Uمǡ$g1xW?_JALGygLKVe&_olJ=)h<.e!{l ~}?#Cw?9nd'UDŽ^x}=TuuuM,=V;<ֈ1e˞,;f/lDdtق{~2#" QPP@Jj"""R1T__41 R. J]_e݉cq= |5<[ճyy8m*ߍo08p HM< {ƴw[}%c}7Eʄmb_ ŋrA_7[oKJx թp$YØ3a=H&?ేo"/ngM4|ĽtCfAټv:>w%{Tَ[o +""-gVĶW2#" ~QcgHr[A˝%kvaه}Tx->{~[]5fssL:xcH=3NwcQ5gÐqL:x7ߑX/8cHkVN>?E.ufApl(lv.߿9'_IHMRQҵ,<7Z>2Yg c٬W=6&*}:9/8dlwM)))|P_&l{ݧ/Rӟ{I聲5[݁өڹ&>k泮l?ҫbzΩX57d_qW_eum}s nY*{o}Xt=ߙeX!w$mh2uecSc[̝Iϛ&K׏18$~95ctGPDZA}ukP6]C][vM]fD!撜H0` ZTl涫5|Yg3Syfn [ȋ=Rנr@ p L$#S;oCE gi$y0 2N]kJL'Ѭn)8RC,l&|FL?5c2wƥP>+fECH2<"I.Z.3ˌ4DEDDD"uڷ"a F{$]-ƑaƑg]=Yߐ},_8#m ֵV41}Іs؅95GRDIe2;5ulww 㸡wl6("Be [(mZII!"R.3CDD$ZnĶHcR-^K2`Eqۍ]{lf3S>+F W Pfx(a_n轅/'Pm}?~:3C;m⛟T[f˷dwLWo=ڲ11سgq^6s]B {ժ)))zjƵkݜb^mͥPhg7Lc!II޸Ӵo)eeƷKX.g3s Yqy˥l. r'+x^)pR22˸f\/by8W_ /<oi}Gq?\#Oؙ;S%_SgJe.`#0G)GGs9xGt?+gouGq晩M3_7r}8X$v3U֭sgud_xJU$ 4^cǑ.3""VeƘz-MNu퐓{eKk-PPP.vŲ("J]hU L`  >c؆ cyq,'X|v ZSN;Mƺ@eqzuqaQ!˗6`睇ѩSzazΟede"!^En\ntFN;<+ L"K7f~8>YxzcǞm,?İy}EocYd7_ϦctLz;[4IAUED/GnYn}1DNt_o(4p#~<DzW']ċH95DDDEqM"C2X2:S\\>Dbcb1#S㤓TW^|A~37,:9/JuMa.""m"""i|ѐt hE ~8uEDDDZ"""HC""""";DDD"Żun0HۥzZD$j8.""""""".j!""-* KHP HĴ}A#̴""vh`k血H;8xLTCA>) ""<^/_T\̺kzt*폮ED\ 5 OQA>7ֻϗ@v`E< vFmEDLyd̤0?‚|ۮ7QTT@= mp\z ZS.e˟ǝLffv )Θa+ݤߝRIDD""" muk N)/o+>7n 1)[m;{%^P9/MN.?2>eIf5 gEM: /kcߍWDHD 1u).)v\\7RZZMSd7놉a+y IDAT&2S)+oe!J K'bOWc3`'u QZǭ=Z꽴c$ ߷HP@DD$JȱkD{cɒ%уpE͎o0o${GDӚTv?|wcGv[q0W?XW`qt+z}P_"ZFAL83NK3=>nW5q''6nJU_8o xOP)?F[{T]Fű<מ9Hc~'qdgKu;D&]c*6քA}~Z1?LDDDDډ"><)))~x ž;҂\7 sONߥ/qKN~;(> _Xqe;"|~YH3zx9=c&D.a3LJ;nsuY"ه_E0x?}εw\B7wP"XJs}t{16Ƣޅc۲&ek9t[#a{iR2|n20Ö`Lay|[^;h支71hFOG*!q\ĵ…WǙ:߃4 _\x8s#đG]-"""Ǖ""'qr,2w{8ÒKy1 y'9; 'fQ~m: /v"Y {31g'Mwg I#(vl{;FY P2װ+@rk.7qJմu7nV`:Schg;;^I[x}|[)ǿ?'Ws^$k ?xV_)i_"\=6̮/^do IŒv'Ck'r[ȏ{2JUfDDNp9S3w6lk}۶5bJ珚;#sΚaŖ$]wBʶ 5w7WcY+G0]ӌk7x-NΩрFn(v!/)qNR*_\_?{3-=0d&%qX̃t"7~ :ua-_| MƲSoaT]3Itv()juRiƓ_;bX\VHÑG}-"""Ǖ"""@9e%6l?H$ᬳ⒩Q\ƢNu4 4vc~R 8}6LAc&kl![rV4z^pjgxiғ=+XzN }Cϧ^q51uW4~G;@QEQ^Dj9oXX~*#6_͂ϯ\ǟnCZ}of4>Pqo(r|k@b9+wV\Ŏ( \7QSmYaAj.)g}F1(}Cۏ:8HҔ`W2#"#@8_^7222D"tܙ_V ^kC9XG3\'SeiLgW5S݇Wm2ma"QΟye0L#2󙷪67s YDw3/;~H7sh`N`e5:k;ɷW^?}~SϤT/4'b5<*9>h8 N""0z˲߳$ǩ /g<?$],u\so%uhg|| Mc{2NTukPjNs%>#ԘjLj'a€M$PLxXo -iلx'u7~+eK`"DElY* 20䵯Ve_a!T|IAp 샯 rw櫳\"RϚ{|kpH>3Vğau.;)Tĩgft>vMQ^4ь? o(Ǝ&7NADDZ?Qf&,]JRJ*ql˴‚۵X:o>ɉXf4-ڏ@3[ͮW r3qIw\6>sz=U]yD^z1~{"h\r }| /Te{N~&D$ nxz~ 쁜~tpbe1L_.9o}24~<"~L^Gͷys9^|z\z(G~Ea,M[uxĨqM[ aߘ#nDDD2|~g_AvdvĦ 1Nf L?ÆcYjPLDZZe6Wu00O011 aa¶ؖCxKޖMDCҀp8̦͛X2o6:ˆa6nęgMaϮ]8g-Dݳg+i-+ QYDDfZ;vߣ{n7\n#Ɲ̴g[n!r~ ܑV8 cFBXD`96ccbc`868vU76FUq˻D"/[JbbbyEs߾˲ރysfi?4IMKW X狣slW_|NiiaIJJ"}&YY;ȉFf6L$99 Cft**]&.Gbb"^2ʣJ۫/xTˋHjOD( ""r0M8_>V1 ?4DDDDDHpʡDAi"[EDb"""11 u("Ҫ<-";gQDDDDDDD䄢HsRIi9 H9 HnwEDbZmi EDbJH"""1BCDDZ?EDbJ4H sti6s1*!""""""""m""""""""( """"""""mzi EDbJHpX½7>wCo_]}!nyGίQq͘%{LyNhebx=UirBǟb,>s3 zJ7{)ڸ/>笣߇R>xfA҉2I7F^R{]sȺ\$wň 31O""C]Xr=fYû]i=i9Z]|8]N߷_YLIҭc2p)-?g4^ScМ$spԞtJgy|<~`ힼQu˯jPE;7,W;n tHR@DD$yq~3냯 0Id7?<9+W^|9l[5& 斻מݕB!3o]t3k j-X»MHU(?Lb/X̷)k܅\r}؅ywq+7qV;|xhTI ֩pLy,oDJ?'!3Ϧ=7Pd'c|)dyhFE3mP7s9+=Kw>LAn,zޚɊ¸HSQ.{'BwyfG,+F;k%#;>~5>ZeUMi^Fdዪ@j&G97ౙ\wHR]ˢ^Ұ\X-Vio f6p Jc䥗V^3/0TBDDS@DD$)#8Lr'c.̮:~N*frӕI(WVzD5Nlch:pE17}`o[OODDDZ"""10ʷOsOY{ 3pW.uWFa|玿 ?>y̽{.[C+u4h1}?r:[xY^gb@p/C~-V=, k22]OhYu6?hoB|81̘m3?T7H#]tO~0xϒ}cU=xr?$tMg=c:>mlR;}uvyQΗ{6i}fUMoX;1a{AchU JZtDl[;F_^kܡd/cW 2k•N-d\2zJ4K\ۛ9%ݬ<Q%n8w=9|9p:e,O njeua-_~R{d_z6<0!&2'`ƑZ絼oF_u,sV_g٩73*.]wtv!?!/)Cw܊K,ߔǮ(_07&W6:Y̴zÐNc\~m>qsjG9Ҵǿ_/ۦӤT& z>W~Φ_sWW$׸Q28rQ3l}Wl+4:>(""Bq=s )o{՚fI㒻/ऌOEl.c{sIiɷ}@Yj.4.d7^+BE].+nm ;^ fWXOG;@?X;Z`W$59>ڥ䆻~Ϥ4ϞN7*Tm[ظ~5 g<^ǃ7!v }C,yS#MK}3'ZgvctCFdvӞ>Nj\I'Tpˉ͚fbg6+`T'\1mE vs۝F|Vnי$ol 4kpe1q>Μs>M=tdPf>VoH`d3<+amcϼ[!̩7my/\7ݡ7͐jURwx˯+/6(CEj Ѥ&ъ>ad 6Lcߤ;znJZ*pM=DbOY]ҝ|;k:Ul͹]{w?ޙϷ; vc'u!(ax/I8c.#""r̨ʌH[b2'Χ^>8:=Q|n=8^Z4It -^xO2l,r wAxǝ'ˏ'EZLb4*Np: t;|@8~'_w?w 0K˦G\ u ˌIbǾ9~{zuC-"""Gx} :#6w27n bFÆcY'"r|Ԫ`b:`Ncؘ؆I0`a[nlE!dyxD\^nf-D"m*Z8f\y ,7p8|7qYSؽkNbiL"c'>=v0bL{)21EvNW] ܑV8 cFBXD`96ccbc`8686ccT_\ 8H$eKILLlp=xwY{0ol30گJ,$5-MUfDDDDDDDQ@DDDDDDDD!""#qԡHkHP is6GisԆH0j(""HP vDD9؈4DDDDDDDDQ@DDDDDDDDDDDb8W^Oo E| x?';R˯=* IDAT hР 2`sNgLfnaJf*J$"""r\YD$ x{ _Hz6-#}PF\/.T%3XP9iH0nd{7V8DC~?9 ox55OȂsxx">n\_5>0s~NB&zqz?v!s_xq?rHv>ysBF Ay/Lcg|03ɔz~J|pӠ# $?__yqs >!oU 0wigpW/;D l!wW+(6V_g732.]+B~JC^]/XJ)cq..f$H3i5G%DDDb885xҽ^Iq:wBNe;8j-ch҃{&Wo5\raH Gjo4^YIg[ti;Eqs߰$ KOQEN`./I[ۅ׊Pq|[K+Ki4?NγˆvPM' y#"яZD$f XnOߴBVl(Yk^x:1,gsWkxN`e 0ț5+SPt|7?Gߩg@OGg0UQ^@~5z}NGR>+L7K"Dg5Za%Eipvfi! 0jV2Ge ӪmCiV;F\6kl"bV} ?*]l6YSn;p3 lFahrv>FI}hg5%o4h+UY#O'X{ 7;fv9;oOOrF*~|3srA/>|n=8^M]7 쁜~Ý|Pb=~iL{6. cg1te~=ky\42&4+""""x}au Gl 3d6o@ĮpYYC Dzt['"O܎UoaFab;abc`cb&"m-%qy[N_8f\y ,7p8|7qYSؽs'Ns]nᅡ^>lz^^> y8I*)r1-̬l>=v0bL{)21E]Ǻ0V8 AH3"N˱1ñ1 *xJXq8xlWH$eKILLlp=xwY{0ol30:&)(""_@=-Dݸ^v K(V0DDDD8S:{!c&3啛wBDDDDԨH Q""E%DDDb88nEDZ3EDbJH"""ͥ_ @DDZ>OHQ is6Gis6GfDDDDDbz8 f:O""0 0ﮈHkf<-"3TeFDDDDDDDDDD܉H ӥFD( """"""""m""""""""( """"""""m""""""""渎wDDN-Ν;GyZD$f( ""#qԽHkHPis6GR""ti. H9eFD$Fsi EDb"""1B~:OU4+DDDDDDDDQ@DDDDDDDDDDDDDDDDQ@DDDDDDDD2#"+ADDZ/EDbJH"""1qG}.f:O G%DDDnEDe)""|6Gis6GCz""1C%DDDDDDDDMqP@DDDDDDDD UWDyZD$v( ""3it2#""""3pi. H*3""1B~vWD$v9 HH3qΝHP"""1B~:ODDDbHkHPisLi.:GUfDDba`;""Ci*3"""""""( """"""""m"""""""" f[EDZ;EDbK'mؕ^U2#"""""""mWQcB~v88WDNL>"aP)F9z /xJ+"'h~8ꀈp_{9ĈDc=+6:WDN,qUf7p۶cv5'"I͢ˎC"jcT~ c8raXn /=&B\<㲋""Ҷ9Tc P N'ˆ1îĎT5 Ʃq0F1VZ$0*.\i"rho?wc\X.+"P#ʠȁ@Y1"aQX. Ӆmv±tcò\.]ÉT^L0mǶ0"a̪Y3 ]b. Tz^ "#8""jyZW梀j`"m0oDDNX:OKvEGݨHK Cl߾/EC 4 Ҷ]b"j=nio9t.JUfU++[DDDDD/ݷK,R fj"""""߁f)!SP0Uu' }S<~U$1o8cccp\.,˃c1ϧG 󑘘x iVF0O  H aZA1\8c0-7< oχZOc2-\^/08vDža{#!Hö1@vǎ``S5T`N'F"*("L(b߾}dzj]G)))!>!8\n=H+H@2(rh@ì Dz0L7q{p<^:""j R \ysY!!HӉ`8]o@@8 H,BWTPRRJFL8_nZVf@6860*T]aTRyp9UVB쮴DDhsa.CT]_5YFUV턈r{<9 Gz]GݨVg5"""""""nWt^fDDDDDDDQV.,RjNGHwx҈s/iGBs+Ecr9Hw|&=3yg'3YyKl^kSTZEFNۏn31 CG'pXvӟ2[G!#=(л3ɂ֚.h#r'2w{V.^Aqtl^e8ގ}^y3:\=ayޅPYx'YMO9iiδY-ۈk׎K$J$`ս?{|mctӏ`eC|1'';=9u"׮eV~)]IjҡswǙ|:;JqLOu;ksc1`}+ׇH󴴄Hx?[K%+-z|y,Zʖ{tzBŬz%n0]ؙC)#'gG)cOgDZo]_.ÓԉΛ@˪7]@^ƭ)-KclzAv+V2!|2vd.f ֬ۂmۓF&.?z8Yŷ;]zc􈁸 j|mй{_ƞ4F][Xkv+卧 "5eee@Bb!JKK1 C? }v_qq"q:~Y_OJ_._)|47!+ 4GF8,ƞ}ֱ(%ҳ 4hdel]cԵ#2-ZxA~ EaQDDZ%ԓp72{/'~Aq T5CiQ>|>s ;̞l.N<R+Mٲ~3g̪yClZэ8˪7]d鬙|c_*زa-[7U?4%MW$zN %wdh,[Fo!,СW_^6BYaA8N5K݃#bX2w.[1s182ϝ˂ 6ey|rz]eW Y2{Q… )**ɓHLJ_R\ǟ|LFF{&L87}ÞoqN2t*Payq=ұ/ֿ' a?6eU,_g%s.)ulgw~r*UelUi&3CUtEB~v嗂fgЫS{ÔkTijWZG { ](onp.ӎ?%c! v?TYR^]z%2dDv[]6{U/޶P0߭^KfA 葃")-Nֵ T?r'I$/gTFi_ ǁ38eKKJ().>40 >i(k֌h)oK9ܸ c*[>yz@KO4"mdetѣL,&j~Ό֟vEjfgƎ'p2N !`A׍kI4q #Voq4hРAC, -|)Uu[>:UVk~*1#JO,R39ip5~, o y-^|E^~ ZPQT@y(׎]8oQu9ڲHHk0]&)NŒw/f=dt 5G9HS"+tH#P߮PV<@8br=]:g*>KhG;ΝoF#{ о< MTx-Q/W_e̯8S9+JK˸z?u,Yڡ`:.IFjOvy ǧD$X{ovx_l&0*O$ȏ\Q&j2]q}H)v阡BaVӗ9!v7 NEWl,;q28Mtl'"""mIާK[ 1p5Ǘ4 Vr0`/pvҫ;+6dM N ;n5zQ/]+cG៻QZ\ĮYb9}GcTnMN4U $e3 w;V|DŽkOt"]2 %.ƞ6d)(/rwo[ٰLل7a_EUDkAll6ww[bPp9y3bR6J'TEC^>qɌrӰ(++k.5ŋqtڅF+-)9=h$S3h3ZFc)cp|aۇS3_,H#9fvGaXx,CP'@4S1p(-*"`#/F튈H[`lݶ/ #C 7d%ĝ<j[FK뚖NiZ=) SZR]m{<R<s< m$."8dVP"f\BZv6YIȤ mF8$bۘ& *T4 ;#z3{*2!bSؾ-[S8Z>2S='+9E\EEEr :86:KWX*zW7Z=-6]X g֗ 06z52ݓk6]ղ¿0]qtH%o)I5!6ʂ?j +ݗv)-=i[K.=HYRװ9h).'y= a})gYx"9[) KLE{wu6ד^riVMrFzv0ۭ8U8|| ~!?]ڝ嵺 =bkrVL & wV~5"h={vD" @-‰ݏX1 ;`6^BJ$zj`y$`x.?? IDATN.!#`~IHgҔֿUxދ 'wKG:ѡ$rLt}{t%1`[޸DĤFVxmJ2HS" ")W7L7G!`!3|1Х f' e L~,Estѩk/FOp0{ҵ/ap,witblٺ3/ҁS'M9d%2hbq82 R2;qRf'Nb3MISqbiƎ3fl޸HՔD%{6L2R3T.`cTpkia&wxWR+$$r64Ǟ>߽~;n3;cl`H$@@Hu?ʢ-s~S*u[s[oP  f0&+9,!swI8Sx n755;صgj;+u2qD"5+g4(2~[Ffd??UBqю T݅(7AxkBt ty„|aJ7+H봵co422صs11ZO{DFFwȌD"~x\-TlͅKٱ#?D"H$D"_șD"R4C(YiTŖB#)( g<3D"HDzlH$P! "dhQTG1/}D"H$D"HEUf$D"H$D"HC!=*M;H$AHb M%'|Uuѕ`UGAGEWT<4t͈L <&3nAL{'H$Ohۉqz\hx@xЄ*tTtT?t&R w!z$UwDx<*s;Cb9\UʯKD ReEA(* Œ2#k̀Lf0lEm`UD"H$HqIm=DUUR<$\ilfkH$@tՊNOsDQo PUj@(1`LVbKK$$hD2|ʤW[hnQH$`!3( y"B!jDÃj(aтn23»H$$xD2l0Ȱ4MC7Dr^Z&~Ã{-z!@G @Ѕ^H$D ܸzGk BnT pBfBx׺@U@E{kkkBFFaaa3(/nOƐGYO.5Gx5bCC[ :Bzu\k|÷?ihA?[ zʽ{9w,$2Ӓp;/(EǓ;f2q< _:.(BP? EוD"H$ױ-DXp "T5=tB]Nw{!IU%IFsdegj  tkϝ#<%Q볿]@2wS:Bzut]`D7`pز~+#hv^ټ(&hlBq۸r wjuYW+ܸz;F+ N  |~-#*mi̚=qw)d4gob<5ogSdy w:Ύ- [v)}d,#^YBt?Aݥػ1^}P|fCg^z_+н2uEZE$DinnJx"P*3oUf$p$<<cu5jZHoNN{rq:C P^1<;pmO3I0WJZNCS X^w}կQZ`˶QBuboq 9fNG X4S$gMfp]y!==Atm%D"yAh x\ B P> DD2,X,DGs徴ۛn,!ʷOpz lUj&=w,&>9ٹm'W7aDP4yţho<ǘ̦>:xlXI (Nꣃʊ:}'4²椡3P{W1$Q;I(GbyLi8:++GMHuk}a[ʹQ#N]t۩7Qi @c!<.JFchڵO1쥹W@raA)y̙K$3ݻ 8Akgn2ir(JD"H@OC H$o&}?: ZHocAɀuښY|=SyQpQs7Ux>7'Xf0?'ƋlYBqv:9|IY,Iiq]ngךGdw Z]5O"!%()`=q_ r(4ȀtQ6r `ܶm]vn_rVLq9 ['0"&U(Z(qQ*G*6!CQ5n֟ff>8e0jUgdOXD9:nT509{w #}L:~Y^LvT//!tw<*_D"H$D\'|w|]wP{Gwr|n 1$75]4 ŵ-n qrrARJ)DF2yXv;F^fJ Yn9DZD\&*$g̚cB 7f%M(i4Y@Jf܉ MȌJRf$'ک=U`1)띕6n. 5ԇmksX GSnzhjfB(&ΘŖ5k&bә1M](B|DȝMޣq&cߐ`C`rۆ'#p7Io!ҙ D'Uw鶵g7D"H=-oD2driP|/GGNF"BA,Y]߹IS; Pn(@kh>mC9StU$9+@L Óq/~J*~0[(?'.; Wjػu3㱙eh>j=N.s={ݥpl]1,(̤%]ǥFc3(?Ĩ9; (I?c$܁\'Fkx<}}n2?,]CߧD"H$Dǖ$Wy[d`K$"Č.t< ,fC9dŠ> Ml?C"xt0--`}4clFՁdO^Š 'wV|oSHMVv:U#=o(ID߳6 $oK1b5AKX[W ?ssk8j0?"{ذ1݅ߝyiz{RӘU|)ʣj3bӳ1i=CTQb4B1\}Vn?›:C+dfDo7z%|eVe_I$$?ZO:Lǭ: 21ჶ){ {@N{zj"ud6M8/cf?NgU5͟ >?ACB/<\v-X#S+Lw+9{`#gόbܩhhjNq›!p _@ z)Zqq.^!->ߜޘmѨ;Nn[dv:Hʼn;0CzB.g#כt&lQ0'paL< NMII&.J|u<EJiڝnPb \PK碨VJGSwl?.JJuyDr 3s\.pgrN}[,1 %T2ƌ>o@s\~w&5 6A'8sMTzz}t71(&3=q[o=NjN'&}U XuZJH$&Mk]t[~X6FgI^D"H7"NVA.Ϟ~U)X+{/8Pu',"7=HLb:_}LɣF{ܺ~!pk'eelz3A^n&Ӈ8sI%@NY ߬ފYiz')(I;fD>1l]5dtp;ٹQb vrx ^=D܎F*6&)'dBf-\8]s[AzYtӞ(kߒމX/[l6eed12s$6Tt[>Nm{rpj w$~TFm@=~O 0k}oW ;2l/LBP8qQB b{"1g,'`FcT\?s0gJ|vΟ!6} j7O;e0_W#"VE'8D" Z!B!4*?i#zWŜĨX@‰ߴ W̴O0(O`O}3~;6Sq8.΢y`˚T?ãW6g ,r0_@L!L39VWvb?Mvaь:9K5_bF=G3[4E b Sp0WT#SǤ3π;/p~6v8m?{/~ iz+r%jbT.|}莮;ش.sx|F1![=C/~=51صȘ)ΆA;G{#6l4AHL3W=d@Z"S3CM̘?|s &R0n8vŌy*wSktFA$l&M`#xWUQT-;1N>Dն5uJJf>IUBgrj oL'`7+qh%!5O>KBlxS:th`>򶭆3W5"xQH͟Bxl*Ǫ*TӛTA|r:'lvn\&yOΘ>AlzkTLNZ\y@p )LN(& Ngƍ,;[Lw=:K |t7"󈰚c.R{L t;: ¿ן2Dt"D" T,6T470CWxAޓ0J||YpQ{j+LOk=tkדWO&Zl e躓W`7í\t`bdbq S,8پ~%_|74c %u~󅘬 x#V`s b1z;ii8}ɨWƬ9Y_}CXFUL[8e%XwXDEڅls6,[JSxo/rs,$#3D_ADt]qq)D=el?Kzkɠ|aFߓj39Wʽt4g`s3wgrwBLrй1y5c OAp;bӘXB87?osh"YAb|lMژXS~s'ca Kdʼrs9ܱm;,c&umRT|!TZ: #D")Dxrr#cۛUS{9躋ʝɟSKF{oHvD;YFr:|q^ cm[xlA9r>SzlYfa[w1ee(59ElS% Gb_+Sy,^|Wer>&#&ks+>(5, MdɋT;Vl!|qC^3ZEHVB/!;?",CIPDE{sk/MN;u $@VI\ފJ{gC.NP>;nBݖ!5^DkNעH$I,1cR[yLmä)"bF7O5UHpy( p zQ#l78[p)v.\mg⌞F&$d)Ғ>)hs7q;Zffk_NӢpzt0<<~ӟR{ j.pA,^xy 8h0FV, %k&DةkcL1ċJF~![ߧ^txRUԼ <Ϻm\1'=Zo{F},TX 붲hn9  JHX[mv6>yIa੷~=bjꚊmr5{-{=8]PkߊtFy TŠÙA3˥֭F>ӦEyuU5ҷ?k_̒׿Gjb, WU_?{ ffdn!#s,zT IDATKZra2*u84SREQ1h NWߜ Ύ6_A*8km8y^(v{M:zah(௾$fe㠠C/;6׌TZ}?+6nG>Ȯxtq]2S?ȵh 5K!ϻܸVυjd<|(2$˅Jz]D=~ wPToWEy֊PTtTtEţh]3k<nɌJ.H$DQT r7Eq:rEOFr.ZYDXaft D…+v6Gg F Hv|; Mߣ,sG|2].{#gI ! _YeU|avh02SB9rna3:'r~!r UW x6eǎ|Hűtfe߻vo}h3i'QP0j7WH$a(r\"a!Qd6@[h <T)%۲'h;q9;8wl/WdZ)^Oj5C(FQr5G_v]V+?{*緯g#~j勏Q-ݠJq6nᴷRUg[oCֿ!\X߰8-v.u'$e5,DXgteqo#blt;ioȞ<4$Pao:wF!YS~wCyt{t vC{E'n;!(Cf$d8rŀ%D"y xS.' e ߴ@ԅ1)?' 9esXX{|܆FHˑ~2-Ç1~9ypӭKU#mtѹq}{rNa n5QɈLJƍgtf u[T"b(*%}^~{im8o~1yy٘5,_3[ .O?%a,)I;hy qT5.2"y `Dl緓q/F'8^d+xzOzvUvs'J%DD"yxFbJ׮$3c$CF)SwedFFiqIZEs;МjI tT QN&PBGUݟ6\%BY~KpH$GS'_c[UAl6[4,vALL,j)9KĈ %)twh~ݹ!eodՊDgwưohXc&O' vqivnƲ'#z m_}N{kؾq\kH~w"۶㲥xt̚l_Cj)Z1'*ֳjfy)qEPwj?'nxcɘ^Ɛ,%$?XUlex{ DNQML||!u~^zmkD"H$D" {:8s*Ye}?J:|?/QLCRʟdZ~zMشGNqFBJN ˟^4 {㋇\2nFq3g=Éz{MAQ]OfuCT&%Ɂ+ޱw|rR;ftgN\"w=J{)\n8}Z\g٭ Rrx Yc D"H$D"'ADP_sS|Q!^EKC5+r&࿽fͩC{ttt8,ȸ'JAj eƘ, &` .U=}t=l nFd)1՞l!sfq֐oxJzV.fyj! \;kپqĬ~ Zy-tO_"kTL6Rc }ؙc9o""pWOpO55FQ}KgFţڏta4&iQ5 cJsX1o[AlH$ %d ADNN@F orvKgR3* j#3̼bC63G#-KJJOW?|_dlF VȰ iq?c,᩼|yGߺnƦ0(rrcκ5[9q1wo'Bwcw $6v50n9ݘAg;D"%{Z"H  rрzWqߠ暋 pZkg┑r>P΄#zHE&@i=BElR9ѻؾ$7=\Wx&7_4o6ngcIE3zAhHYY`Ն_zI/_n\._8kٰ ?=}м.ZuECO~4^wWs7Qk y7]e_q53Y3 pT;*.E`0r{B YeF"H 3Þ%5mu% MS_PL>Wziii>r[QM+wq4kl~YO&[`c޳Yz -i.(  Q(Wq:IUDUn#%?ʟ(B<6ӓG=\fӖcL_aFu֢_+z o(`:N,'M H$BGSEOFD"^ 7玝'%o,f߄LQ XM*5x+XKd ;.lyԾ4<_1ai\JĢL?\>*-<E'yCm\{eo`GK{8MZu&\ϗ3IX 5~_r}H|\k7p=7o\jA΀s\k1f gb,NIDXh|Z1;+yrnFQcyrB|C`4G3ur;+v᫥8 3sYd%z|d Jgb @{S6wٱ}DkP $md6ϼ"6+GkWmE!<&Is>udRG8t Fگsv,Mg+UXBxXzǍJjV>Ͻ*P9sgLgwۉސ7lF"H$AKgȌ6DD"(w74e*5 gkk+cnwTŲ0>sˢ$AۗDE{1= ܎&{܊/=@.zBgѹOQyJVT+*EECW ]31p,xLf<3?̂kW{ivᵷ==zt]zC456"J$Ip**1?vX^Dٔ|22#[&@4M#.!ԨhnAc]-ёqA:A訊 x3J׽Q{!HS'_c[UAl%4,vALL,jDDDf A"Hi4cZח&BwgjN_s3mAU5.P "H$*Cf$dDrob?L}akPU]>N"H!QU}-H$4H$I* Gxu!"H$AI$$8hmN9D"y.Hg@EE޵7!rxP4*·@UBuDEsCfDH$aH$FAQt2#Hp-H$ AQHU*H$ N(H$ UUp=wq8=u4_Ďm;8v M-X"IMg_M_}A%2.q1qKQw3@dT|q, |眼xl}Y2V>- 1w0mMuz5O-4F`"HNu{/OOArlAU>%D*3D2<F Ia":l ^~id'?s~?._GLf/YhwŪO3aeo)o̧?ˏ죯8\To>7:ӣo;ٿe+< , ]u*q%۱˿o/j{9`IX8گ?!txo_Ū )ou}} DNQL߲y^r^6 UyZ"HDL*H$ϰ1x:yY ~{RbvcO~x=.F\ko`E2zDrNɚ͟ҷ1Ed %=%~۵hJzoK;vϿF\Wl5's%,4ɓSf͊:g 1XW=WMKs&v$|W"־?z}tp7m>AsԑL>qT% =s<,psx˦=DV,L=NkRۡ[p1ΤtTj}:MնF3>1{>~FE5QP6O֝cF1\/>T&'!*h U{H$$Qo"l2#H$0\=SB:Ѿ- x/5u6ۘU7'+wohwG6O3aB^M{C-_|G2(Q8xES {MGpE婜>:6,[JSxo/rs,$#3D5 =?O7?;<f=&>fڨtqC4\e˺5s>ˣo;9"9 gD3o9%س@+'^-aStw -r{ E%v¤&iQ5+rYnjb,pBQk(qD"5Bt]%D ;9uBo!\fe7_9`3uG\Vj:xr$B/!;{|(Fb斕N㯼FifҐSNnuD]H>K~im'Z=ܔ%0ƨO-dǟ?[MdbϽ&:Xj YS@q't`ہˣ#PMI9XB 3D"H QG+ߕD" GAPUe@rcKzv5P}œx-T׵1izf*Dn9srj;gLRz\Fd˞<7מle׉ctP Vbm6ʢ$DTs/-`22vc[`佳D~ѻKgm.gkϰUf?o,a0ZISُ-:~ݥ9=53) |PO`$9;,TUqBUU 0D"EݕH$a56tJåx“H@ݴ;uB,}dPLuȩZn]j!L(/vr,Hz6o9JcK7A3X³z5lbDZ:c20A XχBRVew2ILE 7 xCg^b4YI)὏wsc2$fh{q ٗ 3Y mD)f)'veWݫEѰK3FM XN"H$D"HA2#Gϑ: Kg.Հդn.{N鱢FN|($3r'8̫ 'rrvX^.@@t^yƵz.VS}%Wc P@pjP2Rl:u!-Ccy~+ YfK;Q}6aYؑx\w2B3qvU `%6LJC3y=6գ[# 5PcomF7X)ljD"H$D"W&/ILadrX5z7rG{l0HOpA!)3˨_sټӧ<ӟ=7Ƹ!R IDAT3xp]' 7uE10fx.ɩ7qo`%>@k^/a6=>upoU0*?mc<* \u 4PsrUոZ䔇D"H3 ))]Uh$!#0yV-=*ywYoQZVYqqvl"*{b ETkϱ =$ 6+SBzn>}LL.O eo`'kI+($/'Ύ[ڻ-:p E1eo3QYDج8ښ9V[+t/;UoY;e%cFέ >v;0v._8úגZ4~s>VT#z'jQw;ٸr;"]ޮ*49vu6斏Ǣ8ֺxc~cЁSQUP+۷\z!k%H$A|R2Q1[P+&%[MD<Ԝ8ClV^lV\W}PM3Eg_"x kVo?-RG2ccĆv0™V>ӟoecS_9%Qp=-6rǔ3@|єO-bێ ~ 9AĢAQ~++uQyi{9w+ <]7< T3Hca7_f׫_7*# x͗H  TzQ^l照FQ@UU"©x?~&5лSPkk6W8wxۥޅPEޫ4c0%q'N'SiNM3nzcHw꺢c}=Μ9+9ukaی[ojŨj풎GH;wEmcYY]kf-NJOH "[tNA 4Ɇ /Nep )ԂdZ_Ӫ? U~J2VI2**2$H2H:t:N7ҙP&brP[FU5JKK&/?Ҳ]/`iq%NGpX8й 2.^S:tȨhH ,iHO+O*4 OϖI(7EQ8uSj;Vk: lߺ`:Y=Dt_{^Zd#bXd#3 +CM4U9{r >>>L[$@ f3Vo/F(+wwAD \>!I۷[B ?o$7Y0-Lf4Mk@ uH$ C@ )@ Ae@ 4F/*.TBUܠ)HXy爰pASLc L4gرcO(O)W+ z0d`˖?uK7(ҿG7 r i@ J!$W[JrU$ugu9D4TcH(*w@ G}:baG=!½Mr4ӖͿ2I/>ClԸƗ[eqYpAI<<3:U+ѶEh?ų|r5Y{%p>3?}fXNe Uun\2%1mUaf)="} Mqc'v6lCaO e؀H<ۿ7:MkְP8z Dos\I1G1>w&o?$g9vw* w:wA>j?Ӟ`=m>ț pl3Xv#3sA6ۦ# '¿Ed]8Ŷ;9qĒE_r /1ѐ-Y%g?~(f,XSx$EAf^Mc1 , 품,?v4{'=!clܟäg+?}pPU'[/όHKgXbK& bcy&䋯gRGl|Vmɩv9ɼ+KLdžM?`xW-fU.:_X|L{XA#^fH#2 F͹X6 @+]<~>58=MbteyXh9n dz#Yr/᱁#v~[7mbgyQ?i8pmdgy:fM$y7i.mڌń0thVJǏw,[g?Pe\8FOXϕe/`$YvX5T!ڰQ^wEQ{g3`ң{W>2.溸oL'^>tՋ]QlwbZpVv>z`|{Jq.my~uGQQ0{N2qpW$ہhFz@ wqݘ,^D}Ź-*IpW=[KK@uU:Op6~5'MXD܎R}{STf>TfW z %D4R!JN@"]OROcզ7gch=52荾vM^ s7NFt!-[֬ȸ mP.%W~f$~0~[Tnv(+U;+Îqb+94CSս@pqDld*FQyVXÉshݹopJ+5au]`ϐ ݑS|>  ,g1_}4VrL=B[Ǿ') kK~IMIr\gYb-2dsߐ,h Q\d)ܗP!K{NT)!Cz;nG9vB&tlCd^0 D֙>x0P;D=$s52Nx&F6ٕKڲwh;'EyvMCݨND#"Iږyka ]uBRZ4B@ [QINtP^^rE-&ڶOC.WtB_OBSn ¾ & ĭj4"i.-[<=KTx0fWE\dPU7%%|?;'*ǯBtҟ>ZJǞţ1de> z6[d/Y &kcH؅.05}0؋@?}= jyl!F2[[%NrD4` NTwY}CRAptLwdzl5fct1GS(7˛AH ζg'rc{RYOUny q, :aVraKhiQrs^/Po\ʝ tFޥU񄍯W9itHA_. l^}xBڗ[TN]"K?,5^F+Qzv8D.ܽ/_l='G2Pm#TM\!;]r_dI,ݝs@GL%R ұ[7Nvsp*@ӈ7U'u,$YQ/QawomS:c@N+aԴi$]챗as=A_|f<>5{i7h1Z:w!fS<i+j|oi!1< Lq;&yl^ʞCx-x9cƍKQ[|mE%~ 0K0MurX:)*%h.oLx Z߱`t2T^9ٿy1пk{$1zAzҵo*A^xBjlxNCj4Z2e\v0zlB-Vu&]*=hcvI} zrfϾ!z qebji\8KnIq09Y4SEU}h 3F1}&UwnDzIcGgC0agՊOL5{MucwjX}hgjHOPd4Tld "x+ (bPR\vt8lQRR:FdGsf" s\&I~߉Πc2[B@qy?4ܪVN Q,`%&̱SI obq>\3DRkŶ1 Pu]:rz>'4^}0iߦ@}zv=?0I$ngLrqNr&nPBU۵3|aUFx*UF zS"M{7Ho!ܝ-KrQR8˖lBU}x9гS[B慳}OՂ4KwoԪ0=CF/֐:>Ur ʯM1\<^ItJ%gi+=#*3usz::u$)._󧎰fܢDE_~MX *rRH]CgC 'KDS؜*b foUvMuSZZȜ^;Y[Uu^tߕONfj8sظ(Ƿ ʨX|1YjMNtJIĨӵHNSŻ$o PnLjfڍ|h.t6#{.`7?VE%r1}3kf(eLa#97ZN)霝{h̽:oϰfnQLxʪe+ǻrbj-y)zF ~6Л0d AKѴo+`}Lk#Fz~ΐkӐ$v<6>W51ҧע7(~/ףyy-*ZsAR9kX$QKޞ=7ğ#ZЫ#=g[QLu}&1[8;qu}kܛDX\WEwb![Sڗo93DTT$I&]*OJ \}K4@":%b1qڳLsj%/HF&3I61|K:xn5ŤG)}[U}ú_w+Hv8xe*4ɱ'hYU43/W@ca񓕛'3tXOz ߛѵδa{ER7FzOH? _9*A44"wgC1@/ȱcȺ̎>{P:|?b:Bبw(EEk?3.0?57ZȞft*ݸzCݷwo(rOSv\FW 7\T@TDԁwk(Ml?UT {s~Z69BÉi#1 u3^B2!V/__~72Q <8Ak$@c˰1K 4VxeX3 Fr`)E/,wfѳ!;+˾?yqBЫ /DwhHZL9w9=uhֲ@ -W3r^}_{qdcsqtnBcZL3M`Yo?{0~:wgO8}#^*hj>Q?BLD |; G/G9~tGͪ+89:[~K~}tzM>?|y26(rN#i~ اfsh-j& L`\kl0.-HmkiHuih3 ~DSbQȚeO!I@ ,I'څ29v{>{Ӿk'L&=Yٹ.֯?DblD(epd[}@ >e)rWɬspΛl<܉_v*k(hI$nh7f$*I1ΡF|b FH)*,0OСM"UJZ4ixE+khʰ_g|;IKy,'-ӗ)&)# bɳ_8jƢK(MCW9En4f^M2m>*? f=r~1shZqE&[?NV54wXjm>{NM*C>-t:eee{{5X Br1M7oaStĥ[@ j4UJtxq >v)0rNziŴOtK[2݈AEؒ$R9zH>y[pdh-pCh ~ g%<;uNԟǽ`JQCB|I+c<& I_F9 `RRUq\]/J5Bw#jF޻A y(h5@-7GqV&F@#7'‚BZܔoA`0p`ڇӤj_eGOIF*?5IFEBEFd$IIIWN*hU?4 P TMEmaލ_~Opv#CF=GYY6,Ok&MϘoa?206ʐZ~3OIox:õynRf>ǪM ,ǝQ.R7<.ș3ݲo0\#oF|ʹ氣L1<_iDi.^j mF2AskRSz%3kEFNihe$5:2,acM -7`4 4/R4Ņ4IDU@SAS dU> Z_E@ph4b)c@ )*=Z{'{O~7y/}Ҥ)LW%m[Ljx<8Vi v_2_ BX,AGc;cSL/dút):jMאoBHHXBNS5"Kh*R+j ""l[ |@ )4;zOCՕa=V)9] ^{ۇ_|ͻ/B7;0t$*7b?r'0'LIE51^C"@ v|S+))[UTݷi΢4YVVF͆<]^d^f憣ql9L~YZ8JOdrKo̵8}s.#d#q:3 i*M%+[ls\ A8=R0ȞT(νĖM[8z4X|K$G/P9t|N_2!tޛA}Sݛ.=1>ze,'.G&P}Xŧ2uD54vp>+ ݖfbr8[:)1עv`9uG_)輆ȵ~FȌ3(z#. hBћx6M^N6D Tn77i"uz1qmYWƢ~^}}(YĤ'&pl˞A e&&t/\Rk/r,C~>x4~V\2.9 f !u* ^zHp:*|)W3+m8qo[SٰOk&?0^ٸxlFWnY'`Kwqomc;*rl՟=Erq`x3^Fx՜╇ԓ$]F0Xiߥm;vfƭd %W~}҆h&&1IfW#ڰQ^wE%vbǫ >'#uۇ}bN,V \VC1U^KOBٍ4؁==ꏩ{бTbӮLq;09n~>K@ ,/*#h5䆍HVV&ܸFvɽ1J,1(*Fa9/]d#m:duԸ|K|fF/Ͱ>(=swS\ԺgPJl=lZS\nIGxT {D9Aqlض2;@ʰ]׃*rVu1qهOJ¿&s Çw,nVںn}Ild!+: N׽7qQ'MTs 4́MkqGۺx9u"zw+FRg3u,9=jUu_Bc;!j'),@ ZȲ}0p )).m̝m/̶ 7Uw'vVɽ.1hd;1,]Ҽ|״7?4&͉YͷTWda=iȻً͐N Ɩ}ׯCrxIɠ*6̟GoO*~V\ .a߾}Gnx,:ٺ|1ƠxfC}-^:\20aP02A@c}妟amlOIqsR Gݰ962.a IXro[S;yx33Zig۶ ;3)|2ny m !StayADYܽ-w13`$dYǗKYr;w&44F1^lFxPp[ɽivDv;Y8JTFVݠ(HTISX9DԪ2*!KV}jhHRU:+O)>Ml&66>^wADU^zm foU.ꦤ/zd]nUCys`7>nmb/f#t> :1Mf`::]! ^籜l$ހ*ZS׀DDbf:..<ֹlT?F=YˊQdX |) 0B_w/B`໇dBUowvq*vIW+>ķv3T.?BQՎ`%&̱SF-Bn߳cg~q:U噆d}hc•FᱜUs` wODr/[I&>#=6 H}{piVNf(/ŭ`8!U-91ɿ8YΨzb:3m8XMurt1"$`=nwttgr5U"%B#7; RF.5=Jnpy޺R;׳D!>~I6ch?m^͆]);q9m\8}_'vG"fΧ)чЧsB!\NvQwPi'a[IT.4^$oۚSr7,ft2V^$5ДVrd*4#ߥ7c|j Ee+ٵ~.г]=mW9$ݱ" ,"@ ihܴ]R@ 3YQH;~ڢ&ČI,_}&iӱ;& enގX~[cа{_7`N}T}a@ʖ-Xx>lmn<`z*Bosr+}R|w TmRffiQsÓDbSkm<Ǧ[_)9 obⓘ~jMJtIIU &},l?@tax;}Ƃ%1ejdŜv能Zd ը*f@pSiѽ"O^dP:rrGh|J9u߆'S `rV@F4.a [z<|D2WrW>:n?=Q:E~?Cf?KOo߰~BϿ,fɬyS}O6̌םx\|aX>ε,l }E>VQ8'\v"""WED2w ˦`qvyDwˢJ `73GY[83f02o'cN`T6`צ̟TNr~8Tʪur9cQث7߲WS7CÅslxo7&`ܽV 2.sMWN,e=ƙyj.-bʬ[u3};ˌi|v-[w~?X.J bL3mӇv֊9U7n[g~2&DcbϚ:.hzDsh=l/'i;,_63 C/I[ww?Έ~>]֗W$  SJ WI٥z`ّ5G$BF4SnBtIrJT|M #2q>?Z71{SΧo޻;}ÕÙC[xp! qt|c)aϷ=z w8 }f7OXm^{#x`~?z8NW.c0_PɹYrVZ߭ 'ڥKw~!ȉٶm4''wkvBJcU&dgs˄X\ɢbێ;W ̒V%W ZU[へ}lN0Ó),nEC ApA=9dG3%"'ȮcݶJburI@3'+xSٕ ݣ + \=UՔv9OCT?ʶ}Ǚ7~ gb2r2kp^tcRPڟh@ԇB ;>ܖcʨx[cT].+q[oe۹yҨhPK,j2ex:<ًyjjȿ*]BzL9,}Z<{H-;9<^f{%Er:j"CGE_'>aM2grSww>cA.ە-w/^埶grҷ%=R E2Vl8C}B864 Yoj&5 WmeҼȋ!<"66S}24#|yiCDDD \5w]zǗϴ#X>Ą!l` !c?}ŗ[kOxwjm,/&Nq1lX26b6Y]|^Tuٷmfآat҃1\φwײq.j/6w vG 7IeD;@[۝IϜ0kW_CIQ^B8xcg> za]I}s,wU}T<8gkz _|侤|[NWcp.WSi |L*!ضSoJ~[d]SU hTVn^r*ՕKt^{z7Ͽ|$Q,,wsRRYC*YxUܚYGNac]|dQig*a=p0LnQ)nǬ#qPWu mo 33qTFK\uL=K7igNwhnzŪ1BECq-](,¢1 'Ě߿H}urjէ+&m᩿Qҿ Xr5!hlw.إ3NW,aݖ<3w^CMUEDj=w>AmCOlFN]>V#kMaQwYk9sPx9BՉ}T}œ:|cu2N!;}OFV8@1e1A6xϪ2o+/K +]Zdr3,'K ;;o_SH; cppuuy_~~|خ  àcx2^[|U}->¨-,nc,>E}Ps3𴙪P{W[Y_gᭂ!+_n /&>_1Y`o&͇w/}BmLz^>_  ; go入0<0v_lgҷXE )\+/9{CJXuUod?ݳaut8{#Vme̙<:gy98vh?!K?S#ۼ_?O"CSGY^{u2)=ۘ >\O0&8~֯X$|a +F4b8o _T{y⾱~Y|cYڻI@S=_?Ddǧ<(Jy;2k -!-]Fd>"""re9<=}|3Gqt911g+'PS"Mg[2Qz0Nݛƀ ]4rxȲ]dx,`s#6 {/0b-M.4O.㮸eٸ]$hj9rmW= Gvn_8o|m9Ydd3uL.};"r-K͡;Y4WeoE0DZD>?d#""r%8: 9p Y3y"'rN27ӑS1P6j"e9773' K=D=UGT^ ` _3۲h?,_V?vSud'j˓C߲>Æ!NxדC+ܶ|9<>zY7 3ɸ\b4 g9uEy3a^_w?)_[EMC73Gvg>496$d ug^)p/hOE5jxW>4?_w$_zA%/9-ZԆ1O^e9h||S[Bϟ֮D##+;ٽq }&Ngp¤[.Sͣgke]D!=pr(ġ)E2!v~^#R/$ۍ́/N0|H w,˨90P8bF3o&;~&?1 azrNJDDDR),,b.i*;:'u+b'O)JR8o'?+V]ҡZ'Gcղm.mˤ'U|m @wʲ[x$|n.L#' 'sysǺ5RU@zr}ɳMg?'rg3i06݇O ib;oĎ7m\Maǡ*M^2Y^7튿vKK9r5_cܰ_=7 9y ^e%%P]8X/J,1:i:'/<3=AbߎM{4%9cʂ!4?K⣬lDyύl~;gn6۳3Nn Nw^{̰$ͮ {a?!Nmv;7%0{U>mY-ו4d0kc,'ՏV5pEDK#WV);͈-- +*o˲j!h.r (۟&2aLyz!j`ۦ/3&]>&Ϧgطm;7BP!Sy~UVcQPܗY '9Mc鲗Yďm)}sh˖?;˗ 0|dyd[AxVDX[[Su9Wc}O>o/a+~gʬicpYV"pӕ5Txmj8PsƝѪ+#}J߯"D~8dӉS8kj۹|q#yn2ͨc.kifKc˂`|%*p߭lX5x/D7-yڒ;oB|3?NCәynf}[lռ( n A9K㻏30kT 7ϒ1n> $61!Ͻ€9w2. Z'(묁i|Fēҷq_b¢ۯ%8p,zp,9YѾ(QRoShX9C^tL5nWW)_2FǸC\)ڱ(<X}J˘rvo]rf܉nef7Q8zgO? dͤl{ӣÌt=O3{уzVXoD~o;"vZ4I:Ju+M= Zh6I*XX&ri;XB6A.'tCȄ.\.qa4UWRSyݶ奴W.\N<Łqydee_P@A~5M1x@$ ߏ+ˋ8 V(1X8X@ ~V$;2$DKg!\Ꮿ2_"!}~#&Ӣ-Mbc3k cz,Mn^O|aw|xh|;o=y`۳o>$T;cڰy⡯NqזE1,~uO_؋Kr6]8ɫ/ά$ݛqy;3Q;Luܶ!;âb;)1&Ȗ;p< _s<9Y9:O?[A?@f =LnPS'ev u8ߕ)0&d%w.cse!ض``1vsepB.WWV[R.`5q9nL-> IDAT̐rB,S#>F+nDD.1BɯI,ˁ@V( v(mB0qb}Bb!N>Zc㩏e{"Ag̾nOd /-LxplQ7_<ߎ,f̙s|qcC/֑Z?KqϨQͯߦ4;u^o&g 8o'}y7pӂn̘̖e OIQㅪU>n۝E|7+ۯ5W <͕ܶ rzW!ze K1}I *^_#G1l r6ճs*Xy-ռ#̜=ib8C%l׺af8X[IɤyNwF#Xx^Y͘).*e9Vs\hN=@ 9}V9;²= ֏>ip߆ujJHIgShRg1h46nzr͓Av~>[+<`x<""]ӧ+VnfYšժH0rhcpE46cf` 8V(ڐUTY&B/`];zigx)S C]m5ǏeQdl ˗A8lLw(Qo""7q e`9DŽ!]Evl*Y˘xPCDnH9xp| TLn]|Z= =w߾߮t8=fw%E>=>=F+y4k<g-3(uOFsn>cΉMu׊'#q#{8sL~341NSϱe>zl; ~|jVO{ya;\l y ʽ>DiN&拭_gmgףI3ne1~mo(X ;fm[s.I9cy}2gShbC6k5퓛7?Ț6⛋IiBT>='}kjnfZWeD~5l al0,cL  75a떝]9{7'EnAOAlɁbKAdPڧIÁ""%8Xf,q p)m.PXXg|Jeb}+V.EҺ|TDP]u{};r^py!_{_Y r9 ~ًl0!>/}JJ8_}c5 GF`8@eVƴ%"r=eegz)8Luj^ѻqmF""rXN o>$7XCDDZS@DD¥DZohijG'R)bWDnv.$ED+,DƊ;0DѺVb]0Hh5]FD xek0.v$6Rb}Ch?HQ-;V3UI[xG,6Q&~AI4(D&f4X Y(|$""Ҋ1a03D,r¸ƃ!ђH0ĉm=m&>Z eDD$m${D/ hGuRch?IvئR[2(- !pbYɫkDDD.2&R.iɌC 2@K ĀeVdʈD( ""i+HBymof"""]`[&Zq@‰EDQfк!""( "" >ύ޴1v]bHqN M:&6n3xKȀS""1 D%I(if  Pmtxi $+JD""" E$("""f%|M/Bbnim MցHn ƨ_|ʹ%~jR DDK!""ҁ"fTap$t u.CDD:H'e,ɣV6w""")c S;R  S@DD$I*;ʅH"S֍H ""]H/DDDI""]6Jψξ DDDDDDDD$( """"""""iGI; HQ@DDDDDDDDҎ""""""""vDDDDDDDD$( """"""""i( """"""""GI; HQ@DDDDDDDDҎ""""""""v""""""""DDDDDDDD$( """"""""iǎVIDDDDDDDD$( """"""""iGI; HQ@DDDDDDDDҎ""""""""vDDDDDDDD$z@DDDDDDDSDDDDDDDD$( """"""""iGI; HQ@DDDDDDDDҎ""""""""vDXHuEDZ\XEuUUW"""""aV[D$"d@@Rss*+s5xnnfx¡Nԉt{Q""""""'=7nOtוۍ}Ev%"""""uȕ)3"""""""vDDDDDDDD$q$e] ڮJDDDDDDD2P[SJ1nc YDIJ-^s)t8MHHGXHy22CMC%3D, ǃdaA8H+܎㤺ȎEDDDDDDD 8""""""""_5.lGI.;2eFA6lEM~6c 8~ &+.1HJ</UUUاΜܹs{""""""""WTb$+;adegSU]qb{zEDDDDDDD-+;aq1?ιsU.c͛IENDB`kvpm-0.9.10/docbook/typicalvg.png0000644000175000017500000056173012770324126017152 0ustar benscottbenscottPNG  IHDRD G,BsBIT|d pHYs+tEXtDescriptionWindow Class: kvpm tEXtTitlekvpm*;\ IDATxwE?=yD`@%~F9c rz) 1a@1gTT9`$%mݙ:Te8Uz]][;AY3ٹ{g#5`.ehXT9+Źqff>JjV]t/X @qB rkoæ/fv6] X,4kY$+\gQCYp:7 MشBG8/ue9W>Hm&7VWIn$ dhZaf6]߫4Gx&їԦ2iSYJL${\ˀÉpqU-H >d/hq#\r⊌ ⠖i>[2DAAѐ`[ ZMu([]YfEyEUeѭs¢"ztFvXrmJK\mE"֮[ώ;H^~>]M# Q_WϜ9?Ѯm[ f(((! rf*KļaMO P,zi:"v30pq)TG.L~ȋ$y׏&Fv&Ҕ_±ȋS!l2 Аb;Ӷ]{|h-5 c'lۗv WVyTP^QE᧟ָڋb\>A..DAӠOmYrzBN5 br鴯 ljID\H9FVVyp(6\#Fqa7 a 0$;(g6!;V]OtNx*-,Nr-ÞA#L~N$gf&e9caӴp*%u&quS/d_rJI;HE}(U`&kҗ}WPАxcڮڮd;ϫ.d=EsI%pRZlI]ӳG:w\mӬYsyyTՑ_PHII)neƂْ(>`i}'_ӻ<]I82dRԞaLr]!09ex/D"E[HEіf,skX#Im&;٢LW&i*a(6Cmq{a2em5,> Xf]9{̡) FRX}LJ ` L8x"B/=g$Zi,2Q.cu>%C\ /I28cdHJ f4d,f{ڵi6g:ڵoy'5/|'セmf`9ͻ0*,_g2\իUWpbmb:kזѡCjk*x~jر#kזpӵ#~i}ƟVj~&&y$p^ԧ<++\ x<+T"6il\BZXLn/ɋT9D?|ߓ8گx;޺o$)^'k/y%SdXdz\eGn'Lj2s/0G:]]m_rW9}ױ—LHZGK)XwA޺%TvX|ŊTWW*Z4P,O!~8SQ/}px"Zxq^xE.@] ZHye f5,XKXV-ZҵKT JV"? cr󩭩;gu,}=DAA!82&EDz+%f䋫 Dُ(we[do L 6Q$"#R/yi0o+R6k>ɏ0|LYKVʈ!ym0vn[?b#C_p8WM& *N Ԯ] FGr0q.:z-"׌;ʹ#~ ͢\O؊1gv:?4&u.+?a{ѯO= _'P"c܁‰~BY,჎λ]vgQ 5j f9>I_[^'KLj nňi#.<?UHAb׻9dL8bےn>ivt0nl.[,&"', Lv<3s eg2d\CY f˘ς\cꖾ5?4c1O::PǂW'x>V3.c3ЅsTv? ?ee#xx>(sks2U/W'p)0xyť .˘6{]s1{5I{8ߗpHpc F×1&O<~p)o9]}%w=ŗaMݙv^+ o]s7 ̧?`~MBhտÙ0w<,+1wag]Fjʩ,Yf\>Zw$~Jw+g{npeW,- yH`(6,d2^o&5Mtx]7zѸYySxW8e7m2|:kҟ Z0=k  bkZ:**+m8-Z­(ٞ ƍǽLjx'xδm[0Gus@-)+[v"{XO26X(BlE Hk׮咻c5اTVTQS[Ku 6[K|Hm(z̧yް&RY#^i @ğ"2}GY=$\1ݶqq="?n%U/^ai\r,gVJdjn ^:|1i~ahUN.4sFUv ݣ~5r1̣?{2?|ǓsûP,(ҮHz=TDg d/`|ڋN>7<:k箾.t#8~@:t]Gj_,ʡ; ǖp _1:'ZFBV=Lg/I:D:iW>0޿*ƞl_?OdѾX3)%8C] ѺzEnt(h7_z=WH'Th/84_{ҩ@ Nc]@eI\xJ~x^~^n>3ir M- VrnrF|n##b "I6]_!{ T2gMdvn  X.Sq3iѢ/2Klt9]w݅?>fO0m[YxxC4&?4W\v w2rH4M`xmw\BiNУZ7oǏo7zZz"uy*fkPT\x{ϧ+ӬY)~V\Iaq1yUPhRJj.]w/=7ю S Qf'UAؔY@?LvbrNc=]I鐔i7CJQM՘HZ^'m +bypz-L"}Vv`ݘ|ϵnNYH~aD&XI7yO>T Q]У:F -L0H$h./$f{>d~8X=BCj/]:׾\JvcKxNxg P[>Ȃ=kwTKحO|,? DѶ2~^ >ዯ hU+zL>sO=++VO.D]WOM'n0HװxS9pt8zfkㇳMdK LKikn.c|x-MK^W~-qR~Vxg*nާ%Eb[4H=-8 C0}jp]-NukkB|{ֱv?~cbE_dm}Kos+ң{`:5QWDa_Fe~STT@qq!ŅEPuIômӆ’"G K|K(iVB6m >4:C|?ANSƴ;M@magnϷ}o+3i?wwF+KwqoуzqmG1$|X1SgŐٹT 8K|$W13WQ_31G)$ )^7<}?W(:; ݓ>/ wMY?'>}n?v:!&8sZɏ5#čp6kHW:]l,r{Rbiar.ek|IhޗM*e}yжFp]- ne̚V(ٮY> dBXl)JN8@*Qzɫ3̈́eӧtkۓųzF@m$ʺ 1oX{cđQZRz"b1{OЯGcBa +(য়6B;eUB@3=9!mȗ#ᶔHހ9r~O9Gs6sv?v9Y7=e(Xmm|F^P_[K0EE%R[fpЧ>yjB1#b"n0h$,zw;!B+~9hQMO :/i D~Q>"&C bD&i tbzJ-L~A>!M b1F _G,q G _P@XD.1%0d}aa1bD%5iX{\g7~yOsH8vrU^v~+E"BE-"  QP7'p:"~e5Xm;SD@6%ڎ9 X~׉gelX=zбcG" Yr% /Mۗv%??ߗm'5@.H`~8 E^Ĉ;(ĈT!w!3$JÒ#ilɑ $:rAN <qiiۇTáZ~z'M?N;qcSٍM/ ՇDYDZ b ArUΑ&AōI rK8k 1dHdPd o.}"X>J:V q[A(3wQ9_qdiGPP'h4ʺuY/V\EeEE|2)͚7ctmڴ&//mBe*kbĿ~{AlJrDj.{$mǿh'08h"[ׇG>8rB|„]z,{9|8}L Bѣl@j@SQ.l Bwsw*+DAA7۾ $^̏ec.trN;бc@`z>%?/bJKK0AUPPh04R$#;d@ċK 69"1 6Ibp"87$.JYMnd 2 rK~m, %qޔd@w3$B"L6W"ğ E((4u4d,m&DC"[$"F5[A#R?# %Jmɔ&!T@<ጫB GIs%@,5 D6fB("DAA#Iu( hZj,IΘ5W#ڐxm6aǢ b܈\N3Mœ5"=(cf%U]lX|[ W$.n N$S)'JOC_Zռl]S>չ "= ƄW6l\DRH$HD6{!KN( DAA71߄6?3ƠdbAٖG½Gİ2ֽŁ/:vmf7RYs9I \wr;9OR  IDATD%A(il$+n3s [Vx:9*o ;˼)pQIWzք9CǷh@}odM%Be[)((4W" #KvF)@AM1(#4,RFݎ]ڒ_cu4YsgYLOu™ Ś\ K3hKPBYH$Iv1$fy*Չ=fAlgoYHgOa9jHA{q)ri眽qw[BTHo"'$IŢK\5qLdoVUDBvPBH'B@1ȜI#@!X\X-K$d]1'鎰^7dygĺ\@7byK[bV$$10Dow .u$eiZ̎z4:AWb$$w"gR P͒0d][SҧZsu3g̜1xGB. /m֌ݏ-qVB KF@v }&= ĉ1n&#VRrw*(Aq$Y! ;$17NDIzÛ(F@;\C@}nܐ$r>fw oCn?)8GZ0ĉ ȕqT\HWijǸVHߦ.7߆"B6sf|#=Vi k?ɡFHA^Kn'Pr۠v9m]l0t-HKMr"'o ]֞VL11]ky7yB`~;M^s~HN F |.܎]?e9E0:r츻u9k#R $|B~u<3IJHH8ʻM3ѱtߘTs'kR/eɤ%m7?o=gz==#!-Kl8H{G! ]_v%0reG! [|Lui'V+Ou1 $HdIb;E~H2)=ɷQV7svBt = oa؏ t z$:0%m'@Yc|y48>b %$n0 \#Heَ9X~80if@ylY];#sA((B=߃tV\ 9 IYXO*v ("Da C4%6DTy2;LVE.G:kLXMi%䯿l #i@~؈9zRy: NUGiӾ/uu6ՒF@6HZߺ&d m2`ֈAQ䴄wI؝ 1W9$BmE$1HIs[Dֲ㜴kS8.qk@=x2<Ҟ6">,5=a.jE: q=6ED5/lQP0ySN'uB/%R AyoEB@Z| 4e0DXIb3?V#&&Ile6Զ[hILӜzPE6?!Ј0#]ֈn+g4>92[m6 #{RLTÅPyd0g$b0`R$gJH$^%I[ q"-tɱDnIrR ؖ3qm'S(h5l.=s_:0{xXbs5\L{N:0܎!Gc9nҹ?{OOÚF*huO3ƺhvi!KR)V oMz3c@1vؓO>l1m8f|Yl7q镬-a`k*EG]l`$s 1e5㟺/YT#m_99vGZt*#GsVkvؑm8&=bs}wpNwfrMFWZa?/eQ}( Av1W9sXxk||g\p6.Im4zĀ/Ơ͞JLIWj"l J: P*KVxٰXHӜ$Wak O2 k2M!nY-wźb}Ư+ʅ4qUFdN \5 B%6 įHL'>&%ia"6TVGZ?9"ZXL|!wH0%f6bF`KH`i=?DHҡԧZۓ\vW n^wJ`ds<Ǭr]bb+ylet)>.k1_4 wI+6W΋X(<߳be|r x^lnǵs7XyL<7śo{^gGe|[ { >v8|> obk(s (Kr];_{}U@6ĪWY2bv=&'{McWV h?^^t丏!RkC- ,&LyZcݯy֫."݅0P]oݞv׏恻jX|[߽s^®#W[p2k{o ?kvq͐5뫙~ǽ/<XÜ王;9~|sˁ Gm6"l#H[ $Gߤ C։p< ȃ1Z2/kڵ(EktTьI̞pm抔&/rK|[t *+ėT.H,l4 .(rHDHgo$a  l\>1YenID~:!!R9:R~] Θ9oر3Qt-?7ۦ_&S7+@_쉜oЎ_9Yx2u#Ճʃ:;x07<h',֋yQlU6ch1+v/W|cr7Y2>tqky\2a/Z-b֢.p~l*~{{i,> 1+G_r ?^/Nzk{}{wٱ[4I֗ڥQ&Eӑ\t\t>|ңNN|E\Rs#oW;%8u6W##}xEr33)h:X,x=iԅ]5p_3!ЬJ9&H|( ˞Ȃ$I:*D3%#8Iƍ,IK!# $se&mk `8sG-AGHW}dI6䇧O4 Ш գIҙa'Õe&ʌql#N?C$H$[* ,Es;4JAU_H:ȞE}9R.zy6^3 *噔}'}e|WG֒Q.YV!R؋wog;ұl/o[!eQ˰FRUz DҪ' no?u 7E=9ddHtysNؾ|~::~틲 ǜA4qi=_ʼoxu,^֕^1;7{kڍ?auٟm9.GDg;ҞfX-U5 Oy5Y̗ ۰y>ܓ`Y(:$'ە|h>[36Sgv;ۮ$HRZ$ͬp!V2#J*,I8VHC-_-4Ғeq1_2? ;<$ E~bȂNJ3GD @0"iC*#?zֿ? ˈ؋EqsO-լ]F$k6Z_ LO/Y.>Llx瞜|^<]E)[䕶nc0.]ٶWHCy8VGT">kV,^Vڡt|4=xGtl.I7E텛[t 'x!=%'ܮ's(݇ 9B}s}ӘǕךm%Mw4-/LK"v͞Q*$b%o )(I*؏/a4:`WSe5Z|\ Цv\vp*$ˡ-ouE((lJDc:"Ju fP1lυփ9eǘʲwj ؽu(KZUǠٗh:j ZRQj'ъUT0i6qʹ GJj%{mdje??bۋU-?E༻rV-s]5Үc&q K+Đ,;~W. Jh[XÚ*I?V25v;#9v,crZ/zx8 sU*XUyi[b*lk|]?Dn;3Į)#I$JnD ^KoȒTaA5DnѭJ@DM[OelZ4 h çCusQ&>[nD÷d#@FA!zKWB=+j<>Omvgkh#O (Sf.|w(Y_Е]bkba+ br.aXP?Q3v:`W%`7Av!|rLQ͜κ>{ubcGsȉ~ϭc`4qI^JWPHT,9uE٫ZZa(+f}Znn.OW|+>2'm?&څ/%îMٞ϶fm薏͎Gɱz]6|^`K>}$kO-mh??68rm\z綱ڎGSԆ|\ƹucҾ5-~>:۔vmh|md35>/3>>EQtH45515e,f*\{W]59 ]N4~XǘgIϮ`u I;oq0mO vC\b~fe3k&11b4Y] lGL|IWƞVʦTfnGAA||BfC֏f5G֞'߲/^!(-=;ƵేoeWPC>ۇcG_.jD-{ ˾f{hsS9?-NC9q=oӗqYΫdYl 9M}-i̫V{ fc"aIICo. "BRf<~M@A]f26/$[A9k&"2%E{\g3ٔ~BfzapMHbXdרs6Hs|첿*NM RY A@A!#TS_8)(P=9 >:~FChEæp[l 3#9 Y]XM%]dA: jRhFA1a7oDkm@SH`mfwsM~4sLX(DAi@B<=2W IDATy{ G|ۤҘҌzM/ݮÍ+~Jbʌ)av9at m0|h ôk߱q2D w}vGȠhylݤg@i5K'dCȲ>6sa77%6v 3K3L\M"ib'6+/avJcA8Mw X̐πl͏P1\+anBȍ+((4ZuHkH3i"$,6SzZZƆ1")BI a(e~3K4^ 1glwVwbsH^\ZbpC]Oе![ǿt&4>6Y8foy{q:aZtd {>#7wI0}sLHjt.!Ife|M0FZa!𨖉$~H:ȩϜZwHKE3#Fx4P39?CVķZ0&$W\O֖3WyjME((4eh"%H LZ00)]gB0dn$$,U/cj'=F%g%IH#MRY=((((2$6Mv4 Kb2_-FѲ MjjKyTHvsՑr O̎щP䇂EELzarPBcH. ކpܵ-8YY&rz)#E.WkI5 %L3%XrPP{@Bh֜°Fm]=%E:$_?7}+YJ, Qbgճ1`v@$E 䇎Hez4[ yF"%yJi*BDAAAaS!H]S: KEx["!Ļl4'X|[ &k!|,469'(tޝ>5,X F^84)!H+J}ҙ"FVB(''Dݗ>H| =A$ k$I$-o&Dt]ub ӉiVȶՋQ!QPPPhJ$РD 4Y͖0V^"O|(%ހMtr/JPć5U~ E]euY"1V %%P(Mk ku=6멇*(((lha}cEfbY46es `Fp&5߈OPPPf)(( xWbEL*P((((lQYƱ2K%mye843𓅒]5_o1rf-_@œN41+CAA! TVVlj Hr jɌ–Ľ[sDڊCCkY?P=e2|[kDb'q1x1Ez((((,jtoͅOO{ `6;6%2^hwyM 顠E((((()2>2 Mrֶw.^4R @M46{e-}!6D.PPPPPmOHvzP4tʼm۶%g]"G*b6\H_[ Er(((((lip%D٦/ֱaKa3BIfݛ?~-yX~=͚5|vQ!H-[~zZjE8.)(d l8G qlqUJ"} Ɔ+!2*+r96n؀MSSS5jCBjjju]" Olvd2@\u<,QPPPPPhp$D]? Ψ({#tE4PoQPe2 |FAAAAAaK#!b? 1RaSBPSPPPPdCtD=kɴ^~Lm?J=Tȣ? /K2=u&Dr NB]ẃG!;=ov5nlR}$DRwgzC:Mvݕ/ޖ::$^o+olǧ_ܚ.}wK]wﷸxۭګ0w I&Яno_p|j?>=NArP{ +>_|FӅ_I1ԑhx_Y-s9c9w/̿op#gmD7&cJ-& H^:FKa.<ǁ((((((((((((~  hkWI?W~McWѧ&ǮfbzV}:O6%[~ǝugT|MǡΠbS6oŏ$mԮO_Wf, }:/=?T-}XK]^KXFЊ|&$mX[<|wgS뺮'$Sϸ9yJ5v^#Id!]}/O:>Дo X9yHׄǧW×_o`Ee (}sKgi?=!Y¬_KN\'LQPPPPPPPPPPP_H#;^/bЭ([Ń|{:DSӓ_5FJs*X<譔ʙ;&|+k8ѕ!RMaø^V./Ng;p(Ѡ~;t<,dZyjY*=0~`.{7sޠYi?hWϘlS΀}9<\o;K1ʾxCa7]1w0\vF?Գv|<}:2}_&gF8 ֪z-K3GTl?BP[ϲ*$jF@2zWיeOˏ:qqvbc.ҚqƷͽF @Wd|3`5{L| ޛ-UA*+wSéri]&=w?߲![ŚOݷEfSԟK3K<ӎ͜Y39-cF2JVG6O??ִϧy}= bt|O^.4YEEG;%Ěצ{n#)qSЮ^/ڡ1tkQ@^^)ݛag"YƷ:rQӻM1ڳ.pd"w?V"3.}'Ϫu=9`v&/o~d`ntZK#nPԓjyK>[{mEҽR)g[ˈJ,{A[]lAr|ytC%!BeFAAAAAAAAAAA!_A4\1aמ U:~{Qh m^MsI/M$SCc98J;I o#d4ucv/OOYGf'8Xjiդ+ $sܣ \W>w,g39sŃ4sݘ C˹lMyhZ1waT048_P3)[)3>GAlf8YYsne.+@k btLf縆Jގq>N@H3˚E7GvjfGl9fe!j.,5qnk˨᧏8գ[6W-B[jn+FĖdH zh:5oO8}nh) kћh O/p-^Ciy#rszR=do~%/?ˏ8ׯ8 v>t ^2^:/ݨ95c"elz"9IKGd 7r.zQ眷3c3[+'>q/>[{^|wPS+\u5W]u5Wx2{UWyJ' -ͨQ36xݻ5׳u󣨹UUݿ翼G=XN!VeMWYL= >p p6\!}l!1/=nz+@a {x?L??yr(.M=|~^s$|λy`Ol˯綛|l lL:θW|3/?\-|ϰ7#J{ g_p W]b *7>ʻ3i>fc#~A>D_~ttq맿CdMK87554]ӹKu(Ġe`W ̏ \f:W1-7?Y\wv͏rp5yyM/#TW4ohh]O4E׏{X"`>v9TGXH$uΗ4#l3;$jnA8x*4"hZ_98@wwwCyTJJfs:ohh47ikke)10 "Kb,l؉!Ts!-Y(-hzZWubt%ׯӵ/߁l#Nfks #䥛u MZU=t |{^Ě5%<9 Ug"En|uپF2.bNA<,iJcj!a~|c;\cŒe ٭zf]7=)х 66Q("BťHBJKu:9198m+P'X|=(Hg2$S)!RfLHBqYi)N$d!:;!_ Yf9 rA)Y#N( -fRF a5Yz!2Y:>Eh*ފEqQ"J.i=J8DDڊ ĝF\.LYr%RJrP%-:;=mJ7cf]ըkV.eE&J)429Gy䩒p8L4C)E""|4Lt2뱤XR %޽cc4-&<-EdCmN~.\eʋR $M3\OŸZ'}Na`r~.[1h,L&[*_Jx+ϴ]iaz{vR*4bxxiF0O9oݟ]|f7ߧ[diO(bzjg ʋ'DGX\?{7{HկcnЖ-,x#Bwuu}ZZZ¼]}"˖-<9>{(|呗9FƜQ Lkyh;c<6—[JW^O~S_BeYXeYiQv+߆ap 7񊗿{~!A$ͱb`[crI0ߥ%KawW\r1R*FFFH&lܸÇOAcDt8j fXiMl;b2}K[|n BƳ3wxGeӤwl !'~.J:$$JR{,AI]3H14z;o>΅:?xJH%mRZV!ҷ}X-}tmp[l#_,PѪ^~2m-K#(µdz# )}G "Ύ#Ll\q.86KjrqB(xNljIKK eL&"'Eꍤ.q:>+@(U|Rϣl)lm6?(b 0MNᏌ ZȖZ4{4ӋeYLOOq]!h'IO1h'_t9x0D"vrj?oJYh0FL&q)%yΔ)J3$/Jr"m342E}^W4_nm]tR*|GAJɅ^8o)5IɊV 0"ljtp|5J'V(J1o!u]%X0Ղ6|>ՠpøzkxC아٩Dyb{J B@ɒ8−>R d?vo1'OO x~"gn?ޔ?thF>L4+4ҎA{fUF4ڊ ,EryIN&eIdiOg BfwkMgN(vr,T)_ɶ.z82M @d Ga`}iڳH:]5Gs~% 1 `d] TŐbTEr8KRJR&l%"BV-bb.^ɿ+_BL7}/ct ?$i+h:EWq)(AcVfz!B,RT08R_2E{psa)3ݒ "ut%A8# (kټtFƈ5!d4t:r[صϕiP tX,m?D8FY8*sǷ^j2 6x߲d˘ /{9>@O`@b";wX,bbj_(0hB\ץX,R("L白\|O32OD)=Qt/KukHM \E%LGS+L !(]V" DHef2Y+Ψ{kγrpvTڽ,FOHadFc(#q=IڢT8x,Ls~_Hס{&_:w悁&dO (!yUՔ218J 4-^pE^ ᣙpK9`UB+Q QRK#DV+]0f^xd"⯈"uܲV ͝ ˢДԸot"<<U>`Y!/`6mY!rm+nEtc/Vet)295kpl~XEwO7߸NGĒ~mFÂb5mclkmE_4u7w}߲u&ށfMC& Ds_(Y u]VmL*&':4K]҅ ##?B&wK1DZZZF(ٸ|8uG]U!1rӒm}EkӭF h/@IXǟP: MHLädx*idU _+%^GaBLr$glX {3wGf_>̚%1s#qH l)]_pb3܉Q~鎂QBh()x~jTh9 맜mcՋ1wi}?M8T'g]Ka|/a5UsF<,Bvk0R:x+/),-& Lny)!& T#H!yb(L`0)X[\q jV5ǝR#(CGD#$/OӴhbV)إH$\CҋKbIVhw3^74|FRy## [hptrbOJj& df8Nuގa8Niʊ---xGX$JiTVt:Վbs(5s}LC'TgD2^-$cH_H53iQ r9-_M"ě%ܺNIr2OG2"MI.p|S$!Y=AΚ%IF9뜺ҭf8sO%^CaK8_x:y(f1hjjZUtԕ8͒_a.cp>}fOU5k^g!^\9 nj15U>:a>o|#_?oK>IK8e?f;  ;66ZT $jU'2j Y^aX,] 3.29=A*Ukch V߇G cEX9n96Rъp8L*VbiD7#S)eCLm/ݏ]i7UEfטkUeӓ_>skw]пDji4Қz8$᝱]3h0_7x3=(VbKdipU[V< t S+[%( !5KL %,<ϫ"umRH!RPT rJaW >4 u_]>zݏwTy^c(` IDATA[j_MH\w/mDYԷ顝Lk.y<R$&綟/˛h^ð'SXҎ<1]g,bq`fJ3J)B0yF(.uK^}ݳ)xd.DU؅(Nt]L&d2X* M8z9 kc dNw^vy{>OR,87G?AZZ[ua7R%|=bH}\{K>GGO"0 VsoW\Plh٢x;bjʜR JӚq,;*kEl⼞sY۵Q x:g-9%=Hg0Y Z7~G=B7 Y]Eu.ܸw9sOoJyKUrXΛ}TojHJ)6nHS MؼyKuXOjy \OtK@ dVIDVP^29A}4z+>̡o݉ }t;!$Ha_1RV\Z憮YSY 9i0+Q4MDy]!Q͏#0j4u/Y`_j*ʠ||@zK1t&tçy~zs鈯%\q]|1'gS.p9gF%Z9?VVz-4-CȀӃڱqpɃ=Kz)R8dBk~G[wZ7;:Uc2D1@:_$k\J)IR3c5Pɯ8$JO#^L羟L 79k#m"s-[^!?~oq銋~<,9[+Ύ8! 2nގe0 BR#[w \ei\.W Z86X4/rJ%cӄ֖҅&bÄzu%QYlN5F&,4e &MUkt0$SL,vӪzܹtxk~j-Dj%IRRBu"X&)2 ]]45%L _X Px s*cD؉RH0t R[Rge-=R(N1D(\|k-YZ!vƷ"vՊ"VϧWtu& :S0Jh ~) l0z/-NZ|J*'CN0cx!룭k+_;v<-oy\nz}kVY~fuB$m[eRYg+QӡOxm˗5T68J@RUS(||}1MtM+Buʻ|뭷sckUځڸ!/۴ ,(/~Qu#q,(p<+*^0dwEO_] ~YϿ'dOӼ"L`NWȧ=4>iH]/+.ID@Md/?$ܼ M140JN3fC(MtO*&/DPP(T:aDZmdZ( #zB)ti!ܽ3t3w3ٷk"=z0ZC0 K,p~|)nKEٗŃ-%,/%80vShԝv.C)U2SEajjH$R=afC]p5El0kd >JA҉G t·zaJHc&p6r_WL63;wE>_9,.8^{|_a\cY&]H+n#O>T.R^8LӬ{vPUfs9 9淼3Opx,?T2NùC$D8m(`|jKX=SzhzIO?{^n~eKR܉勚uzmrӃ+Wz|g}yBU*:O_a_*t `:Y)IFF] N1hSOߏeWUG)W.oDޡ'ǎ l: tFs8@AAiTrR߾6hڟWLۃH̰TR&Z6oۃN+aCkn7N#)//ADDj raa!%%%vٳ) ǃeK[zps9% Fj`ܱ %?p$Whk2sΐw .3=^"A(ڐWl ̓K}7atMBbT* >lAxBa4ɰF/u!|(Պ` W'IZ,K8p ,sH}Ak6S/ hf֨RP|9~0|FEy{ @u"1 &vm">,`BeYdТ } ,Y҈Z eþmG'ke_P]WS;=Del[* 94UstHBEKFEIOF#( j R5rrr9_|"DQts lߺҽGzf^cժUtNIdU[r;kVnlko+wÇՑkZz}͊lQ[EQ$==M/#S=DkJ َlTDYd4!WcHHkSԩc0&C}9}9 8#ZEhmj;9Z-g[}| =_wkqy%$Ɉ+rre\?nRXWI2O_CuIIDp"%eq?@g^!;8`dC8dN: CNmʡ]w.w|2C8ȡ(ZAהn uy_#ohu~ķ}?,ruJQ 4#W HwRG:8oJ>(aa~N'sB`[APJK:X‹Ȱ#+ǃQɡ{X'=9 AOfm RhwPe\.^oM"^^x^Z-M!eD& LF$*"[ZZJyeX,?_IRLtJb,XbceƳe3 v("ŘtVZ[r^ Mn^b#HAV6$µЉZuh+:h aCEqq & Qq8="#"/>^{n~ ؼe 3!" u (8z GF#I/R/u#ٟSī*:nDC+e"e ]t=:*AvCB8u䳧P@O&EZpL>~GALt4Zv;һ%G!Ƀ$KB4hE=i1pK^+v1{9ݣ}iQ(Cx2xඃ D-ඣ)4G|]m#Hۂb9, #޾};YU+#ukH 14UP)pٽ{oˠׇa<@DOXV, (rv::iaf"ztAZrQ/L^^^ <,fsPMKM\|>aaX,yuWХV-n&1 5fŁ;*[Stp8jbe^+Hb00 mVOpϋ-:W WZ1[StN=HtݐȕHv; Ռh!nJKR@ߣ4DkxHEb͝Hv#2sX:lY3ɖEgr[z%-" 1##"GQ;͊:(L&bby$DJKKq!VDFU(bj5$Kw^)Qjw#y= aKq m<>G$o^t:ХKh?-EЊCxX8 PUUEll'^q.N8q,tRsowOf+swm_ zCՑʊk jAQ2{exصkWMޢ@LIzk'fĈZP6Ɂ[G" Q(j$KypT98.IGuSX.Z<.7 m篁([vxEJhd`"  %YA*H VeIIixcRQcPZ^U?d@%ĩ0϶ עjPv\JdEA#* 2"yQtUPvLDvh[FtH4pJ@`gReLE@bz3ȨжZ+_vUVR^?1-PV%R^e_|7.DžFnЉor=ԗ&,$h4l6^o-B%t:Z"(h'Icp8on5o~:@..E/ !%&!MiybL _6lٲ VAߗ-[z$р,KTU9HA!22`m5& Z#{pb&‘n󡫖VhM&S򢢢w2 1 LURf TŽmNˉ5^#h >1 aV:=PK%#6MoSDQzDUIIy!Ըht:t0zE`Xd-m 'hK¨ h:( l6 z=E(Bdd$ ui4j% IDAT")Ȉ=X!`i0D>$^d(* $$`nn6_>K5߼e g}v(EQX|{%=w~;v"/FѣN~\#3f;"*=%I#OsjɽV>%s4b&^aN'eߗ?/ǦӶ墠UD |h&%%Yx۰6Y0cPTyT8*I~ŔWx\n\UnD dKl:fC`aD3ǶAX5ʿ3=W=^2LKF Z:*[R%\%nnUtBCfhf,=>J^Co#`Fc{.Ȉh+#hx]N<6 %j,/qN(dG W0eJ={@Yn=F$9) ƶ). "%@@ѡPUUC!vn׹5t&pwh +ұ' G2N***:m WjMpn7r Om)Ec$3d*"3 NR7vG>pTTTЩS'l6YYY[)VkqPpd0`G|(v?UA9V'AɁΩT?WO 2xShƜ6suO vm PÕo|={6x3#%-*_D r%Mt`2C`&5 aOj$KII @M&])9416 ƿk"uBڷRJDPY%$M'KNG m' AC5!ԉ}9QXE I-:zG'֙t’ъ/5jtZdBGqIb}V`7־. NeK)/)DEe%'t"7lK/%*2_f$2@^eVeJ~&]تkm} h$"FłErw\;_ȟYmL&?5#dIEqItsWƥ_ѿP!"`0X 6l&z.i߿W\W\ѪZj^Y'L #(L䟍`lv7Љ ׌#K 10h0@D-*&SEDшGwI:]:CP|߁}36 %}>D HB@E Nm!0sZ-R/b`Qd{ʷcLj#W^J)Q8mh>o'T!WJDUl}***{JUoP4=RI(--2ЫgF#i">"CQX%( e-&`J<(zf""›mI$_h4(V+AC6/M"ܹ3x=(~gH)zu-U`JpB7PQU-ɜN!](h5압x.ZBrЈ@lLL^0 c2IINnZ,]Zq1!-_L<${ :Z[D hldЧhI&LA9yqJPd>İD:Y7cд#Mf'n˔ڵHE-m]lߴ8*}rq9޽U"3Ʋtrl6:Y\t9||tօoĭÅ3r¸x..ݻwm/pnDNn.1lv{Gb6W.'Q[nk46ŪdJ@ՐhMf@ q o6n^(@TbccCjpJn} `Zh=?ދkhHi9юPE0ru kc:.6 E4 D1H>騎=m=^W(v"$ܙ1/" QEq1V' \(P #kHUL&^kqq-***!R\+;ڠIJL݄RjƏw4 ^CmVA#WrEQ$""J|\[>ȒV >TyA#Dl$Ivp==n.7Nˍr$FhDoc41Lf =ZCu(W +")2zг w9)sN YZ5ފFEmb6%=-ll߼>D"GF 5zLQDȈߙϨoHV.~CimQ=SZUFsVXňZXmc4Mnz+xD(066Ԙ-њvH"CU ldֿ1,Kh# EW&tcNbLyDc:$KGQ>aǧlBtAv™4cPkTTT?rSˎ;wxr>"ɞTGo0PZ\Ldt4V&zt,곯r,Cz#~Ҏh4Rv%гG:; خ:jA>֜RjTTT:W9PY]fTTTTTrgCRA$:1XJǢ>*j?T; IRu. Tq?[vjTTT:W9P߁@cPg?(ic CDEEEEEEEEEEEEEEQQQQQQQQQQQQQQQǡ:DTTTTTTTTTTTTTTTqhPOmjk2|M;QVii ;vQ@UAT?OST?OST?OST?OTL&zA[iCzGddT+^x]prjTBAY***'JKK?tQ oӹݗC^}HKKlt**>ݻ۷MOZF9[XXNh4]_T6jT ltNI!w6Gvu2DEEEEEEEEEEEEEE`2um>?-"#gj-Se4U!C"RIkb~숒ԻXV\Ck>dqjk`YWGg4c<'d'2f`S|ozVP *k~taЩpmM TĔaWSsmwtCP+^rFef9g1WpuOPŴXqʸAfF/<)wp(x߅]ƬwL <k숲}y|zk R; e0G 謝s#/1a[V\8D27yZN[_.OpIj5*__ wϟOϊֽUFUeR'L9WIr)|V+/YV+T{mݟ][+{oewu=C pmTo o7Us ʭLba^{̸$ʶ,ᙛ&p^dfd{(.)>\NH"U(ke(5_iqr >?SIKIgSy\_ͼ+6xsLZ= Uplx?M\(FC~]awnK{cVoj/ާ̘Iwʏ{i4K_;#'\Ƚlo[#WfpF}Gr joIiіbOAT']bĤA}¦W[7>q^JSg+Bh`+c~&gvj vZߤҵ,N{8 noPUђ-QL?)ɤ;l>.o ψɤp<2-0sz$e[ƐO_YrxDOd;f?#' 5yhclN*9ȯ/y'&3#=oNTTF#Uo ⵄBզ׹nz b[Hh)(›+3::(ۺ9\wg.ɤt J1g#}\=ŋybr:h:Q[7vqgs⽭mUj5*č֒l\=ZN[f6ˉ^Yb>]͛YS7糾ڋ_E[׳tx߼.<{ioP9v6_㶞Pm՞ cfl'S b6Ԭ-Ǻ&+NJlZ@`~Zh4ZƳMA/A&~ogU_gi9GfAA06q^{:nZ| P};[G'k!\KvQX 8mwՈqs͈,/[Ky:aL91jcߒ}jp ޷|~ " A<>{&k{ع'9W&M孬ƧA`픾L=}}\E7}?Q)]aIs#lnťEBUjա U`cUY6w Wi'Wf],pIt(!){YDa;6^z|>x.zj !Dho"5?6o@Ǭ3xN\̲v(bYhϮ~.y{!O_;IuF0Y`"#ۭ~x{Rf=oաvy3ymf?--v:~h5`M a'lkA.Gbՙq0ͪkVBR֣t^o8֎SNPljQg9t51syɱD(ORr9Mj1Ei<~JK}U3{p:3|l}!WQ6mm:brѤ@-Jȩ#-u/M}GGx[AHv4|'Wdg9oad}Im"~yf!dDDgev97QIA xߞ|w:1r䴩q3L.n|շ=Ƌ#M۸*B;YY! m̟ w<¸cs<{M IDATIYI|)>Nv`ax]t׿}so%µ\O=Opkx`VOc]A\OL4cs0b/qIf:}M>!v"s} տfWÞTчui䯔kCy=O_g7q{PiխRUe 42Vɔ,oWrIwC6QŗLyZ^w/s/m?Whqmyj)/ hug<²qalfqy琴7k K(+I'F55{}^{:Iƞ;K0;wݦxƏKkoMg~ۊ\^Ls;xq :ѳs^oy ]cs=<X{$dCդcGss.{qЮ%((Q<{<jgō؝s;v_ d>!>`iS rodlT (ZTi9l% <}r6_sM>-(|j.^;8}gLŠyR]r.cߟ>Jiڜp-tڏM<>^DT`I_6Z9tvH?k4ܶܿ#͑%;(u5,Vk*C){'Xd_{$a!}%'z9V^0A8x#KyfB9c-wbX{I 1j w0/ h-qu s^c}0((cEn\T<N}M|gM1~䠰p'ՇwBuSrJx#>jtHBMDr.T[9{ U!? Zb>r =owd@GnO[vR_߃>MOq{tʠuD&շ ^|<*M8Fȉ:* 7vmxt1'>;OJö:(F>SoOR0m4$6b\L0Qd*|3rkpEZn ?Lyc,֡-6N>y9(bB^zj ܕ/ Ϭ^%xDa&_x0‘> \JVrR*DK'sr sM.yMe쭈d\|S"?DT|N1đ5͢Hʆ_eNcftWm -|xo. o'WgLF23ܭM,'ٻeYmk0vᴾemኇN"k &68Fmnz^̓lue\Kvf}y? 5A |5Mٗ7U>#&'h0'nxj.[=D`V'\w{sV~;5nw*piNhv*/ha~^!!RTԚ[PzseV_g]N)go]k;+ÆO팂Qu_#s+3|@H)gm5-}+;Z֨9Gv:iziA,K@瑤4D ^/Q|$r&n,ؾdIh؜R})Լ󬕄1Ɖ>W#T n޻Ͽ̢Vt)}:ĉ{Yg0g$N9G8WKnԍ} zNj9-i(m/4qşN.aµDyqMMr,6\wâ=_orV~x?eYsqݐD\=7} t;sNjRiTOWܿΈmx9w'[7nj6onl)ͨZZwGdUfѼNV@Ut02-)]Cus:C:=ZZ0OYC?0ө#}d(r6WVjusgWa_-% -Ė:BM,r&#ٷZW^^l]t0oC DZw>t0~Gs~ZE/?ǿG$֢EkZ>۝h`HFfM֌S޽\/qdm&miRv70Փ2[2ڷG![Fu^ʶ% Wn~yw1{F# F1j$O7us̾AV!_q޿y5Ϛwg1kU:)nn;8y,n{ܸ˲[yh˿}BAA!^8{u{jku|)Z6f,"|dj;WW$ԉYJΚyj(.yb9e%u\85ﺏb?\}508I7쇿@䧷>Pi1w2Χ9uy{Z/HNzLMWҋ2Uo=˻ֵmw!w/y=_/Cx?;w0}.{l?8?WOHϯkֻA؜`iƽ}CcEi/쵴l%غ}\rNFgwg3}{L~CSᅉW vgN%}][cw\Jd`a[ejMjfj6 /~ҌQVC@ϾZ=:"8AET,Pn=;8O=~v٭}a{vݨ(؅]BQo^Zvݙy3Lj(U]8v0[bcVX<5]j#Ulm'粴{3w0e <^g8eEP':\Iֶ1-C.N<ʜV?-K*.ޥyXWNF[%mħB_r.NKq7Rp &zi<]RAMaݜT7ImkԲ5ye/:ؑڮأvR.֋I1ǽ{cw ,SJso.>N8yuДwG]?r0 U o ?rdZ7q -fͣY}3@[ɀ=Kc$Wt>(_|NDD"ݻhܼhzgS?Bh($8CMQfh&%ҊIPBoO(wӊƈ ?ɩ]]>?_(6}cHBUN)1j%SǹEϨI֜&Yyؙ+oJ~LXX߫Άl]DukLjWښCw+f5aP3FjW` \^Yy0&/r*&NE-[Нtrbӳ |l|j&P BK-8%5Wx1DIWӭkܲ,yu8~JcP n1YWm= d/Ծ%HIE/ ZcM ChV6rVYɧnX5t+:yxxFYGXY:P}Y=Ȏ4oݘ2dP:M{L>?ұN65_x!vM6^PtN ,,?AB*)w,)X)Q}9rƒ !B!Vp%:Y)YSwbj^`*̰[źL?<xiIϖj1_T^QU*2GwtYɲVN=b;S[iɓ]02FW@P;@tUtb{Xl-× Y-[ĶjgCi8`[.L:ڌYoBY)ڠc'0dV6mɂ-V,EE}  7b6]Dcm8'+\Q9bun* y9L0gZ;?D$XػSjfKv< fKmR8ۋ.7Hvڝ'Lطn,]&0Nrf/MYkmleJ\ *!E|m6$f vҹ jnBGoqҢHvi!"B!B$'OO9ao{8I;oB!BM{?. GLS!ꮔ߲qR@*DB!BuL])2] B!B!қT!B!"ӑ !B!Bd:2B!I`)ׅu0_K2w#''\as l3)E7'Wa\}rҵ9L ctǢ_:r,#;eo.`OQ4zKSY_f/O5JwBy{}?GKJC$xDg<+¸UjHzetSVT߉_W捦yԖoԗ^Zu(?*6ܚ1[n\9TTjԃ)5~WLOߚ̲cs}UW"}*/B?0`ZN> &ü$2#om]?w#uq2F5-AN3}rQ(u|=ωڳNu6߼_hf=#+[0j^mfRIuѩc("a<޾S[8X3Ӷƃܻ7kGVoyODRtbOkgӘ)[gPB iz1ź3{yn>xK'XKCXΨV_G-ʱs?~<}7Oq']=i2>jP1]ep[w>2Xm}yf˹E:u̫.DuhӍ2v0k-\150Ƒ͇+`^78x"'Ε:0avuʬ36/fdz;F啩 2vAo{p=PtWbΞږ*ݕ c|jc!f޸?sūG ,tj{>J'= -rPBkFձMr&ѱ59bdk WVp_b` z_cnȝ,Crێqҹe_"nџ+cֱ%{]}ɽ]oxOkAw(39#ݗMv=땀gWx`~2jgrfV"پ+3V0N}XrWcO u5t[~JK8އ6qo6ybERt!}C#ٿ;:x1! :R0)Yο"cJ Hy{ҮvmcemDOL[8.jvf V( yzy}P`TDm88Ǻa (a'w -eUnt.7&ٸK i{<89QHKI8oGϚEg|uF,@Im^I"~ zY^5Ga9=|$e*(i3ͪz^ͬ9Rٙ"Mv$M飧k.et tI#;uyCnvM2Oӝ1*<zy#E()/a^~j{<8-Ny:5qM35&Lh1+˰j[?[,>>]Qw'gS>6*Oqgg\QQo~*|m'Xޤ~oNN_8Ֆ}͟ ȣ.Δ܂k.m˔T|vM#S3.U8B@p~rQg\ ՘Qs=z7XNs  -(^ L^jy}S( Y֔safSVžoWF{qb֝%wQbBv(Zb=~`H Lu ӥt>g{-D&"ٻ}k ;rjQ|4jWߣhi 'o?3SC3柏|hW Sww4ؚhN0Dn8&^?:lxPH13?bQ71'̚J^I>9`+޻ ]؉G~DIQN%Ay|aSA&]*?sjTɝFF{61 5I|1Dह<u 69z+W8 az1+IYv%Axzq3bR,]*?u3Vȕyg3u|^nDa?uif<0߯?8%›}qTxȍ?gQHz?Q$xStk "UqMbX=sa$Il1B? tR<*sy`Y\jexA.m+E$''q9Y׹z(z;pL:)ي$JӤ[A..;˄ą߬3%Ơ7JO:Ǖr`n3eAeJ"c;]I]GEsls׸ptnOfqi&ΎW9`6K0}t&g[d8]xi1kC![>7(n]*?f"j*҇CzQٔYۜX+;']$ }GWrdzTJgxYHgg A\z3}5^S;:Q'p͢۸`}_Fi5 rnK+8:#.~Ca EE7'XwӅճG0 `]7,_op?Z#nwgDձj.󏚮 P)]2|{=j+؋ưQ}Hq~??+^5Oi8oE]_:W7GdC(dɚ_!mA(Լh'<,}]:nϋG ʹ?i6Vq3-'fmpF7vʈbm<.8ȫfbpyKQg,K5MVSgӦCFWlT+!U{5E ˭dFF=lW(rnø)񜼗?Qׇ. W1Ii1!;[ޱY~Eza!8UYx2-M_>yPZ~SgK;/Bٔ7Aw[i#WZ}V[x1!wJ 4=f!s *[swbꬋmPPSGDŽu{Ѵc[jʋIS.˘f-a;qU=^[\-ŲX6[Ŭy?(.\#{\ `HMӄt6)V܍Zв 7ATL4$qMMڗb-0\J>3;gЎȫ.?rV:t-i)sφB50 C6rDJa:M_͈y$LWA˙ǘYtj&~EN WiDD5+hd-2.;ǵfqQ¿/1a5q!Zz:Ͱ~{ ?6ԯՄjȓO#.:l~Ϟr,@dz=+4]ZQ&xh4v5W_ Pw.qh8xk JwNN1Ȥ?4.t0+T\hL+q!U?ȮPrm?ގ2>{\S/\~v2(؎v':"xup' n9O&lVMM󄿩s5|Kգ~i4Ĕ%iUFYRyl՜ wcu_brFUN&*Гҝ;ܺs)S @8ev7Βx9եE[c?^&2&%eWݖ]{MYи^^'o @%0ȋܸKd8; "&/RSD7 Yz4}bSe7rǓkWclB(̳cH m?Gb ]7{gy|})e̒M?$WȒW% FWlK 7-t1x,[՞=Rt|'b9oC;0P\#[nlm),ɥ?"̭W4^ϢGp7,j&t1ȍkAx-jEā-VK%}+?'a9wJ4vr74hVw%IלAu W~_/QFz!)Gۙi3`AN]΍;wun>"3x7tz`cGTS |XGg]49f&Lzɢ,Uҍ՛ gZfuN˘0]`ý9.{7%y^!\><_`Hmw)kڬD[jfɊޒZ\6ͬf骥7lFѭRܛ>eVw2I]In"*uPy5]gܦKiҀI~7rN>O5L"FY=Oiy\ ڞyN4Q(8Qs} uq-EtR~aʊ[Tȃ8lޞݵl5ND c4r dO~_ VC?^LRD/Gz[PS9֧dLeR_$[9[zS;We껞营\udܫP`[J)顃/2|;= 4JLVyB"ştH:݅gU\embb;=;RْW8d myԦw˳ 5' ʏ8v 6%CuO+1eYqvԸ0-dyJOy0nEvä)>gƼoTVmǟ>a_ʜ1鶍FK !Ҕ\[ҧo7aG G= 6<*F9NI4}[|FqAakiGb7 Ύq]ꡯAp}+'whiHB땊A ӊ;'y$ĪilV/MӶ.=e^^J7ƎT-'_ū&iRM3xguMfIMӥ9nz"q>S;21*&#mTg$\ޔ4Z,tm*C(oC}3yaW֥W%uqޘ˯ԥ)iYs#˔Zp0Ћݽ+GQa͉[[4$rF,墮Nc6Mcan\ o̽u4xqxOᘞۂB7\ z뫩+km`-܊z9\-8Uk-:{N"Epן1AKrGv-YW\nzѣZ)/C_Z-Œ߀MP҃6v#[6UN5v3 ꛝQdQL5K:ni: _ :"e}sd8i>G_[-QQKZ=[j  x}S3 VбȡXׁ p'`ȒVӷ.qh.4R'1z6jsFˌ1*&A q]B+ o; gڌr9{=S*Clֻ*ϦbۼfgT]I*WUo%'.O<9s6ySk:X]ǐ ˔ZƅEvq}0a~9fc.=MQ`w?{vnSz\{jXGѫTL&lV|4la#Kyە5//¢=gƏЈ0^¾GMș[1:=7cS_Y{ҟ[ՃlqKLj:벽7m)%^[Bj1k']Jhukd(컰J'U_7k_D޺k=Vwge|.ڦ`#>$6x:t 9!.yһYrPw$ŪT̘m(r3w0elm}[ʢIsх7`PVRNyx(~(S`f̶cZUQ_bX#sxY{A$5,^.LWFx5ޅ˽97# uLؗK4ПpfGnZ]tZwIv {ԧq=:QqwwY_>lSrEp#=r:Q|mFlH3 w#K~Ⱥv4kJνX܊Ӈp30X]: HIShĴ4gڱtzဉ-h鵃JG|i:uTܝB_GtO"O {aŲ _61yEVg6TR2GwtYɲVN_$OSG.RREߍ5\Q16-ҭJ8ѕi:71jҰnSv`_"P`XfӿbdLUI+Ǣ>}Z"zbNXt݁EzH2-g/XW1ñn?&NÜ sr({Ն8ڊ vFQ]!iw|W/Uԛ${̦qlǾ VaR z#:dd%Cn=6^e`{,Y }8r힋g! y>a︸u/ M֢BT}^&q=hɃ${m}h+DʅgDfhћ[/\g5oCތtiZYBBnG?nd$xqwtR9]O@Agvoߊ N IDATS ==9rFʨȭTD S0B!H+~z x[}vR"_y ܼBL"zgDwQB GLSA[ !Bd"nC!BdP% !BdcI붻B!B!X+Dtdl!B!B|AiQ ===>zB!B!H'SNу|l6x]]]9L !B!BC^xNVln%d'?SSStu;ܻuF!B!"9bi#cT&5668iZ|B!B!D!B!"Q@z!B!Bd8ɍ"-DB!B(3IB!B!DF٨xiT}^t!B!B&R|%Ke kφ@BSB!˸yR>+c˾,h$B!&-Db,eگ.y%xhkq׋:3y-zҕ_iNB!"y^Iz"B!B!hψy-]fB!B(v6"B!B!֤"&uB!B!DF>$1BˌB!B!2B!B!D#"B!B!tBD!B!q}Ot!B!BMU}FsRWr!B!B|{RrtB!B!D&$"B!B!tBD!B!T!B!"QČ* !B!ۓQULR!>.B!B񵥬:RFB!B!DFm}FkCD!B!T!B!"ӑ !B!Bd: PR !r!B!BddI nRZ!B!" !B!Bd: 1LЂ${ !B!B|m3b"B!B!tBD!B!T!B!"QD=nrB!B!/wH !B!Bd:U&J !B!Bd$U(;B!B!2gH!B!Bd:C!B!R]fd!B!Bdd+4U!B!"ӑ !B!Bd:T03B!B!ȒnӥB!B!2R)C !B!۔TuF]MB!B!DF*x^Lk2_{62<\gC!B!MZ,'KqH^hF֓B!"3j!T5/;^$B!B2]fB!B!T!B!" !B!Bd: e_\qS5]!B!"IXCZ!B!"Qŗ SC2ygygygygygyzp2 9HG_a迩4q$UHDml?A! D~Mlw9S'`lzV7X.GP1%%yW-g^ Z*1H K{a^Ý?ٵ<70$sik@b~)I_CB<הGۮ iW7BEmдH2w-Dth\d;rMrY /*)oƜ4p#M Α-kX?ۥ1)r$<obiL xy#nGFrQ O[5&q-/YeOg̸d5ݣ+߽3_60F<>6LJ3fo_:PK(5q%w,/.SWcBvY՘,8x"JC1+ߔ@LMe/k^;ڳO®՚EjqERqv|ضINJ5һO8n5O>7҂пCErƞD>VnԘfċAx6^m.:Os$8-c#1\ 6nE.Ķ?o`S&tP#1^ƣyU>oO1%e'ذ~'#TM’# iF{|uYܢѠ@yyIu\vh.sο n;a_)k`{5p&qK~[NA,у"i|ޕUEn?IyJ,fbzI0fhMkK#sxV`SI4lJ95Η@nyIqYeOsNTrE>J֘u{=t0vr)>_Vq58ׅ̺ٝrSP3k%23[eţav9[řظ'"񿶆_R0ȃ~<|&x =uNنEL#[Z]F4xwgE;0=~㵐} &{]h,-^b,Ā +y-֮ŋƾۛXCs&&ԭW3iR1C<{k`)k0(ٚ=Β)s;zW2bYH0N;ڹ562{3#٢/&|Qjȫ3ȊYt9hS=Xo˴1ߛ1{}vww!QCGί?W}N]}plIe^ ? ӦI5+>p..uVF( vϫxɀdH]\\FW~gzs)YkmiON6~fF8Vu MbdTŌ_L/Z4ئ5o9>o.ٷA4$2OȟnjQ;GV_ĵG8 sD7n$:SL6t\ٴP=)ˈٽd1_M$f_yDƥ L>R;=?t0+ڀ~G8$4޴'G9gQ3ȿ{.c۶?=2RgdCV n /!R}d_owcX)0qqr]бR,ڸҸSyBOE(#?qy_7Hr`*ݺ:qwE:zÆʜ|ɀ[޾y՗Ae~Jt,ՈqyXt/ uEm yK㌭-|Oʌ]5/rPgC,m7/ O6b:iG8u$<.kIPeOi&%i8,DV#}ffX<œPز PP *ɛ#x( ፆг36& &68Jy|yg7ŬKm(CF~7WpD~x3^ˢ>9E,ENiYzuy]偦49^~g5A-nSwk7}?]׽<ҦX1#$P΋0(^V(C_pN0`gVxY`g]YZj-|_&k y]J.2q @ͷi uj0qm+TXϭ09|@+ΆGQKt-<ȶ!Rɭ;*61kN! #^ryvȆ^lJe^ QYM1k6`ނ>yü^XZsqdnq˂ *0mmu&` ZA0`*e)^ˣyR g2fYHȡ#WK\篲|j6ɚ1\5&bV_f}~x=:$~?O fU4YvHyR29'mb雼Pׅ8/cf;YކB|FYQ-ۗ[Ev]z-2ZQLJtrS): ~;>YI,xFnIxa,Ud ;6M8e5Lq+]fFLɄBu*`u(X`Xo?B"26e礦 K _Yu|/{óCt3le0~aX%j%ơ~k($5$.AGau#B  3֙ CDžo|c#oL0#!00_nj!os&&ԩWSS ~ wR-.? <ć0#JY1M-Ns=RG14{'y.1虓8`6d⧆8e2RI]YΏJIhKԕ[*tpt,k*Tww?216Gy.3%|5(;_ȭ<)_ K9a5{p<;| ] oTDJYK*5?DgJf:FAl'hA 60*U#B`h* 7Lu\IaiH{ +[Vmڰe&yGQ} dSI(IH@z JҤX"MP?X@QұJ=$BH:3ِMdC}ya3s޹̹< lޜ+w5.]K7g|%ZoiNs%ԜmOI1kwn[B2mN.Xs[lkXoy;<VV%Ox?ȸ8| U4\G(9V>>Fmڞt빽q'͛#{Q`&!-=5qs);\? 3ݸ&llg(< ,&+80?qs,.6pkvPxS,ɝgX*YAN|.Dq6(WOcgj JQ%%^#M)ܑP0V.dT }a٣"Mk{wk\FiT wbM ]Z ǐS7nڥ-uz߷<ҥ ƌA gǑkY)\;ڔϢ4U9|-/ I%\ա=*0*\'%?+3n岕\qf^܌Q&ض7%K1{q*{k9r*<ҳ:>ŸN\YEݒN}ϋrv'*9˩pU4k?6p>nхhOޡў2pX*| ENO긧8hK~)'0d5s|2ȸ&Ь u`-tx"0'7}?amؿXA~չжPBK#Ox}§qT5ף{C曄 l, Z ٸEyǴUCE:=Ys+qV̿3aWXtIV8NT/s"ѠOgtk0SěMW9B *gb3]b^!cB#靡 3r5'Ұl7ɞzTMò"I`sr;K>H(؜ ߱cq]2:/֞y|>s}ȫiTkEm! ^/ohwrtklTT@ڑY }:WLF;,Mf3j(:"y#Z3rIw͢Yi8qoMa+nL^4sod]\M:ڿX|?o,_'ڑӠC?^Z㳱؞ys/?9ŌҕѥOoC€\ ”z!Bҷ>y5=t;2^o)Φ/$->$Ń[kٍԶPXW7X ANcp%ri D\JԲxzYVVeSiGg&FT.Ń/xq77rCHWƏb1lTiÞwDyK} f??$o*TFf0uxd0rk!cx4I`ʕ< UUk//dcq1pgb1S^qy(XjNms$drhމQ# ,fi& qd.ǫo(uɇ#<#Kx6kS<\+b/_Ң#Ϣس=s~rx31|SFEs"ZN;{.AN+>(1ߔdܴބE-?tRovobt9 R q IDAT3yO3+^tҥC}Z W+ 1WPbu5eɢ\2}HK$/.g'#L~AvTl۴*@9%|akE򔐒xl+˾MQ^'^c[P?A4{3mO94W:z^4qdF s92uz+u-~ m@ f̿~¯b-Z>9mX,1 @ k_Qf*ƫ!o}0E-g/.ɓ-1e\n~wWX,涍C{ aC @ }G)JF2@ @P|*V/DBᛵzp J&n6W./t:P;s8YR1HBM @ 6M"h]@pסQD"ԨŃu z] @ ]%"ъtĞWowq]}lj|@ ; WO򳩚C=|^i@ ͛IIIq+/:t˫K&ܙ^%66{vt= $tۚ!I_/M?jGf (PƕO|" oa;ѯxӵ_ϜIY#C0u4B<0cqnS_h-c{9wR΃t{f$#?[΅*X^"̀w`UlOϧOxd{#GZ~ g$l l޼m2^'))͛7ӣG[XJMS\*9dz{ǿ+U_AHzne6)S6ݼ {5;frCxA{@p[Hﯧtקg+5s{=)VWfQ\QQKGYӉ22@5::3ci.G;,9v}yF~N\2ۖ$^^Nм(Ə_|?9$f0S0Ez m| V"))%JuhH(bg¸71̘5+rwFhh%?*V@lLר=XN|Ds" 6e$4,DΰLXH8:<̵Xrİ8i09zHS~JRI (R7пlJ1[>Fz Zjײg;u &^F},R;Y4 B ڔ'.X% dLX%GYGehWBdv}<.O8b(OiKIMAXV,+fST6hDٿ?7ljbޣ3aܗ}!j=+N8>JumwAַwQ }|P{EsYm/V\͌_.*\e*K\gK`sD#>7ooؖ IxNCdBFcäQŇpq/Q7QZʿ=ٻx0Mi%Ɉ~q۬=~#~%9C)m5GU\vS3핏H ]g ӷgrm+v-%?1Y\h$a@pc6[0ͤHK3a6Yʛ&( fX n}xi.{Ϣ_{-y>I_&DzbFBZP='y~]sj f/|sV7/S)bmPM:&FǥM2ýtx{g.??\ռXXPw OJq`*,!3mf zwk[qS+"9G-%9R}q_Wv8 ӷM*A7iF0WeYV L&3zN*|l<<<<]PS+AoJd\5r̵ބ5{ ͞L֠[]<ܙ@P 猖MRݿZVzzݻP S|U+MVeULցdIy[9g&Ek2wt$|k)smpo}ӭ~&PM%p-,#wy-q%p}=hfg/TFFYDbyTY印_Ҳ})k4bRYSb1y+ 6t% .HF 4l${33GV}EK zu0/Nj)m@N /IŒbH~|U:_c,'ۆ2%r:ep{{H}s|%:?8i@ AjJ*:Y^>DUUy6;EUIǝOIJ/,uַnׂB?$8&jN/AHHtshVc…,^+jzLΐ„Xi._%&ފ^Ÿ'kg]G͜N1A˶%|e61m1̘9`*1c@}(i"69ɱ=+ 9UNvАbEϥK٠3q4}N;Š3rmJio2` Ζ%1xDҜ`[c/ʴȦ\Y^.#5e83=BL [j GKSh7396@*Hxi&N_`ÙxEg fcjÖˉ1lKjҽ9M[Αx3hj$բҭWl\ 6ņYv-ʰWل![wkA1c!jtuOՐ`Biif%rs 9?q\a h.zNf;\_9ñ-SJSIWF~kJu\}yn^ Z?*sן%V|Qb[Mj^.K!Mu+ ۜc:mh([g*$w=[.47V^uj4H,FdJY-3V5©Z9朢+ڶdR T[.AÃSx^1m.s?O;3uvo )fLULf5N߬w>@^(\6Ru6AF&ԩR*UjȀ,>] K>o.Q, MF|ȢW#N Wŋ)U>>>b6s|HJJ7HpS9* Ss#FjuJO=kvߜo<֍~X+*Ll;~pD8GqHgL`S2oN{.gyql"?jpN=ΟP^! {^ua?ϒc@?ĩs?O_GٛQ_|rqA4 vޔ\p\%a/)Y ٵS_x~:kNþI!ҕ賧۳v;gϝcg93x3k ^T-х_W(QU@ ͛IIqտ/:tK vD+^2UE {% ݟy kiӮckYp6r}};z~PE?[֏ri3.c;e6͔|>=_얭=am26VͰ,HɿS(ɯR[/@ @5k/r~Axv}P zYWv0) %}v yJ\"g-؀d~q 8q)S:lw2E"QvlV †lr+=]*XHӽ@  _O=Jo^X6X-Yl(m7in$yq^ 䥝{L9׏o&W$X}^}e꟫վxr; ٳuZ ]D7$VhQhݭ,,VfzQٶhe!ǬA"]Vpa6.n\NT^<`?֣P;FDA 6K!X@ A&f+tlFړv È|pfNgX&SgҔ(}љ@Y,wRc&yyq{Ʋy_-*y_w _ d ^~6eƙ=YQϝIk_P6Nkt1czm6:t'wU)3T$]Z7J9kgO\dfE T?߽KL{!Bf L<Ҏy:Zѐ$Os- I/&ɾtlYJeOsi2~|b[[#LjXv]1S/&ԋ@ _-&wm林/ڣ^f|\J,.Ȕj;f3yZ| ;zJU}%5/n_ߜZ2r:q>7f}! ?uYCKz43{oDA 1鮗־&Gˌ`^dZGo41\Ӎ<\ׅosx _Nprpw/螭g7п0v8]Q+-w)qq: je3hwxӖᡌE֋;vcZQmL1c/x>/IHLg/sQWxC._8H> vv;)&'E9]ݸn-t=eIP ݱ.#2HUD 4)<wnwsٳQ_r KJK`2k칤[ٍO.a2JJx*k"U/O/+4*!QjӈN+ lkMtOGq[\vgt2OՔ MQYyLa[kIv>ZRKU3iERI=$,\G5;oruQUшO# v3 d:~d^o$grѷUP&lX$a 7"7kB]@U@$[*t2#OGq[2g.D *ߜW35*T5%*\QxP;ƴH50:#U$/h̺Zb1,6*NX:TUqo]b VXbҘPO&Dq\2RrR.BLr$ e$q蔕s\&:6C,:&Qe BaUSHNJDd`tc:4Fw˽2GF4sK y.( ;Ԇ8itL ez72wנlY=}Cuɩ*ۣmPujH'(hS?){qHxIM=e%+v9㪝eF7\zw[9HF:6|B t)%|_>o0OӸiTU\tO%etBb0G/${A%HZ-Xvʿi6.9vo-1DS54ěA Rz}CWq{4rZܙ% 3 aϫoGUMhZXۑu25 f=NϮ?7cZ "Sed|Tv~ҥ[Rz#Mcg.n"((V IDAT_fCǎ7mܸo//ZigTtɍ%:'O芖w=;ӯix`tU]V% Q<[XtMCsjJ."m%Uۘb6R!z*\iAǣ5 2%J}qv9c2I1JrƏq)f#_]%^ͣ<4N cJS!SRbZFX79N"O# Ii @pߡOh_Ayrtٹh| PyƇNFhD]Sicw]aS5?YÌD?NUtbɫoGaݕlÌnGףx|먊W3&aWf cE^ǰzz_1y "܃*{J@zƼ3 Wd#4Ƭmg. 'z}׷:_#kȜ8meT z60b᫫}!>oZ 5b:3c6;f ?#gΝ~ fϙoo8nRЫ*VN8M*^GJ#\RUL3b2&1V>y7TfUkyoo)2Nt/.;ڟΚh~ٮ7!p>jHϩN+_d[pyܓ$۟\Q :,s䌙ѣGyipΛ#Ft H<;3yk麕uf/x/:zrZ(v&ǫ(F]WU v+q)m'(h%PTemTxEfW| zJ*jHϩΎ"#\d4u pW1.3@͵;\sYCCC~_cCFjܸ03׷]a޿f9P(_ROj dةSY klwP@ƌ*xO”bga U;vtv F}@_7<룰␕$#Q'kxJ»g36V{dhed[ӠƯ̈Wk8ϖ8a%VHy됉UNgo"c>J̬'\f< 38PtljJ*C[3QU#fc1[PTԴt᭦eDA-#gS貊-zS䷿RPmboT2F :y񂟍yהhfV7[PWD4,H{Y@~}tKydmRΧp&.x'm0+U[gU*zP>BMCy/Xٕcy[ȳU$6hhY1qDpYO6x.s^f)zw(@e4w>fqC@QҲshEMa[S >n#Z W6&: v.ڠl *8xaJMQI1N^ }Jf UXyE:2Khcϡ4]M*Z@pge&%"׭ui3$76D2p89m[=Me}s}=&bX5_h Sll9Oztٗ:NX I76XH]˽ 7՟YO5?:uYR7M;mIbSpnX?XNj|FRwg`LDOzUdDeb4VתշAcH&)6Mh;nd/ܷjbE;}EσiToW%Ғ,kN?.XvVW/fW9oaf&gZM;6 S>jc{D2sf1~{tF/)I;ڑ$.V)I^'A!Qz y7F4/*o kUz⭇ujcSD***1??5kʛXߚ.Oa^=/ !o8fU۝ih!3DK[NG畃 oa; ~QQ/.gx lֈ/zLΐ„Fn*X^"̀w`UlOϧOx8_kX.܃4ÀL 9sAR""]:EnϺ۷=ɠg9gV\eV<Ǩs WHߓ%y@ 8c7^ol<\2@4BņZ˰qqt!sgdži1˵,܌p0{M b;A$] IM7}fsB4[Ldܪ*|~KLJ y?_yS%y|ڳM!qd/M˹Y4m3P=3bfM5˵V,% 08F-[^UTʭ!RP]Q6*@pOlN&4-ؒ,0/$̒ow)3'|L>F/%,֍D|S4_^wq' up<@ޣhs? u\txFLLq6-` 4U-(9~M0Ix$bvq?ϝGKh,OG2u 絽0c&߭ŹD;Kpyq/t[AN?) t2JաyoP|nj{zpo,Ɣ_aoq4! q VbSA[dv}<'B]hiDGp:)]&m6w@dBOgU;E@ @ pFBzI%%V.TD1'1=R\_2ito63Mg?o.f; Oey3?V0QS7{sk G+o{Mohd\_,UkEp!CVëauu_(o@ M^BYgLցd jۓ[9gv7|XStFXH0a!Tn<=.p]q 4,s~d%S ى0A3Mx;3q2휵+$v홿ɯ3KvDgoMe? wsI6&0oc<90䭹qϴvn Z5>hy@ HqWTYE/osiyI(DЏ3z?;y/yK)x' l^ZM;o\hFI5!s${; h${b$0l T,@<ٗFq)9=%W4 eKt-uǑ<5--'?_<ǦeJHB ч+Y w2 BBXjVQOТRR⢸~zf iK`Bknt6bϝ\Բi.m_g;Ӭr0a!!}?o/=Z}2k/*~tlcB,"DƧ2˸c/_@ eSx8b)bNs#PDlr.XLxu"+-Y#xA( Ȁ-.8^%;8y0y-|8I_:\L4L'fsRԟ,Ŕ})ԐwF9iH8>K WM&p%0v8yVA@ {#sJvА,ʴȦ3!AiHS$';;"cڲY4v[*)5;}u&iNX{l.sŨXߔs0[>} -58z Gɷov̑L7rn]3:GW㓶r~H s@iӍ*}]X6X-rmr1 FYl6kv̊+{&7dRmCg&csb_1>%L@s ^оe}B52?Ǿō5c7 Zw+ˮ۸4Sٶhe&ȥ-雨gů(JmZ!q(U nh6oLyg끻 ] F'jpfzT:DnDguG ټf獽WM>Ǽi9oƖ_dZTr׃Iߌ*-c( Sl'l-p\=U#4岴0aLSmRc9"&?7m/|Z#=Oq4{Y\`L<Ҏb;GgF~ݧH*(fr[VaO#⟯O4n !Uܷˉ}:f:M5 Qk3C}NO3sm4Ūe,6\_u0;;L,{ڭk& *H(<ϣg̙3'ƶ9V|F|%o}Jbmau E'_):/ebt=`VT)fU! H9'զZ`N% r>jHe])j׵w0&eX^UT]OGԼ^/]+ sUtUW$>Ɇ1]DTo1Ǟ 3ħdWwQ~8R8"_z``zQEZromPi1b]Y~g8hOV5؟ :( H4d ]KI<JHmrԜŎ9#l:ooaz7U9O8`xȼM,%+W]&߬/u TpnU!jOXI[ R2o#!lؙqFf[nceU/Q9j2};nѢtlJ7cƝ9ݲ"1aM+RH!)Ac/q2U}.jS6co+ِȄ_dP 6}_w6vLn}aԃ+j&}Yyl4Ӌc"+¬Gէsp&/|@--ɡ߷&ukA uކըŜX_3rkLp)isURN&i\fStM%AV*TzRJjk^,IRHG ]l ,ڗy`nan58ƶn_޷7JLf57BLcxVM]z]o>sy^tm ~>c&ViO}n5I5ciOԓuaOx箹LGO ;١Ru'!Q?ﯥC57HͨxԔ۳RC )cҀ|!VM]YUŇ(t<5<Ò^% 1w쉛cO$9~zژva.fJJ7liaP167f}.9˶gܪ?CH~kv^l ش|EQeD;1MKafqº9I4$%ܙԶ clQAn- Z2ݙԶ,բZ~`/O붒tLXs\(`ly?^1#gW#9]0GԒJ]09/*&6#!9KubL{<孔㘚u5IcWnnj}cbRpNJ_)e%@L fL{@1=jS'ȊW^Ql h_b]{A4 1wf&.` 1k/ᩪ"9ȊރS)尤lþ\?GcmL-5lU? IDAT~Xż= X4_J>97k>ondɂAv*ei'κ/!>/Ty صJG#5@ % |'CL}0ӷ&1[o܍5suS%Pxb`hYF46']7={h2\)zX6j헧R:4U.64MRrNJ_)e%5Lz!nn5Nz 4Hk/BF8[P>@ cژł[N^/m4J4OwԕpoKIKDR]3Z5JRQ3?e=> #x4P F\2Ӵe%kAauY0ĝ3mVP?6N1g *J1} 2뎧 f|ZF&<yrcI4/paƆY8O,zh] ?< uM9F 5Բfv I;)ϑcu5IS墛ٕ sRUH*+Aѯ3hܦ|I}D!ItFbkG>;yZ b.AR".Iel_ FezqUX}Lo>OM'1䳥LjҞJ%\6)ڱVs# @Mm y#9R ކS+wwaYtt$l$g=:uʯ<WUNi\U)y/E礪UeKBYI,w `Lہ[3iԜ/ :?ljtuuAH>Y JEK`zD8y-dLko/\?ϱwh65Lt;3\ܶ=!gq̴3J+vTSe 70r(J̌B\g`n`QH$$RQ P w2+J~ϽXJzbu3))ɨ_-Ji4uGv jQj"A#D&p8ϘNhځ@㙕uӵrRkLt`Dk3\oI]\ԭS)sRJYI-Ԋ⯮S 쿉Yb]{A2=kZv/QrݏPy4!onsxn?=(MM-r׎ "2&ޜZ(qօ^yf)I+;Fo a@ёyqm7s!OjxDQXDezoLQ\MkG%'/7xT#1 h0*IӦU4$;TAH\7?6Dg FV)odŒ&&:O:)${DrkF1Lͱpfź dQ֫-cPcdĀ*QP "-ߗ/^Q\ gۃLDhں=zzGpSO5AҧhΝ>A ܳ<$==}*TƦBWW_E2' 9(ۥ5K!G_ܱv"8s.% ._QR۽"2(K: B:"DZ c6 $]rʪlNF'Zf%]!sd.5ɮᐂ _kLVE=/ZY5we]sO#߁&]AH.+X fz]E=K.tntxt Y|gJc\k"ysi 37 Y1*JK" n@b2lay|%1>>β]i\6kTHҟ߯Ag qy ngPWg(rݦ};ջ79w zsԵ+~>ϴ.a7WrNF.SE%WGѲjޖq KGY5ch·$QSOŸCxbDgt |N D,1R F4|d< =+a?W1 Y3f(oco?)h.F9,)۰/3XSo )ż= X}2/ΤeȗUcZ rkIВ!ΤeɗU֊{y+A"iŤ Ոwɟ?<~ئFf.W1pm~blɄ^rf@iCer\g7%6.&q3~ΞVJ/ޫ:7~;B`brrfLDe\Фi>ludEmmk\5 \Aӧ°"W|/*LN?ʃ󔂉b -LJ9QIEP@ߜrX0s+GBLWO{.ŵ ykGӨ.DKHG)bG:#Cˊ4N߿/poB^D 9UPM]wd̅`<7vL̎ʍ2iT=nI2lJTüךjes턽"GSSƐڹک/!\_z+oR8"_dVLFݙ'Q" ɝjSV&Ʈ*n~+@3۬Gĥ]a`/C'gJLCeV1?dv-k%uOzAM`|:DFaua1G$pBonK(ɩ%5T@3Sj:<2߶A@֑1_;98F.22y54n&-Gc7&1p3e'{8=j1x7Όtc;KcM//孾ghUo㫬U3gPz u%:؟Ǘv1sVh]$,,='&f:po౨5}e21N"41U>xkOIvf-{p} ۺ'匥#bXu6^^7g6s'a'&u[ApCwz ƔRƿL3J'!Q?ZNlJ C(a %ʌmVW^$ّ厄&Zmy.K[܎z.̠jY1Q2v{/At~< ߢ|^JV vH4!.:KzG[ ipzI.fs glFƣY4?-o_V0˪#&5#hpwc|Ҙe'yuMjNwΌj/xN.؅%kbKNCCrhǔ xzrylKNmiŤT*RJjq#\(jP>D& ]<켋 Sr+A62JF9)ZAO΂a(}= T۬:ڼgzх?w!oB&sdLA5mG7dNwYuɂn /5q0w!:aѠ΂A6 UlȅSb'sWk]0ԒݙD}|)޼l%?GN&2#ԵyÁoU7B&6ȁe)GǢM1_z iп5+5o[ Gv IROAdp#R}gys01ϸ A.yS$K4,--jWgQF4fx\WT=qT]cƗ1@cN7Y} zXo͗툧;7 j&X6|/q4M $۫}/%礪URVR]/oaVä $m3n%ړNͽ[9֕z6iwA2,6kQũ0AOwyn5(H\Y2g'o“e`RmtطA_<TCǝvbꚩT*lY|u6 &,&sk1!xAq'PJGMTf@v%?CeŦT*JbkP!\{.Ak?r0E'+ y"$A@QS/'V&y>r% MZ/1ZfCݙ M{tyAȰ⒴d`2a-onlIQz/NԿVc:f(U5ж~ rS]O \Uh_Boq{ V%V ̻0cj$?aY-d#8>؇US*P\PR#af*29q6a/ EM1b$80lV؇|v6kfN'>ti2"=%[#TJܕ)ttslq@Vߏ(Vm>5("HnI=-UO\͙!Gٜ4:hg!A8rHRʃ0AmHCc| 5tcycL0%s>!K7N[;h?͟9͔6JW^IUʖ X";kϥȤzy] P&?^\rsOsW9H U"g^1Ц?8p&l!o.EUAOhB Ig w7*~"p،q>]KXB#ط1yu@'_CWY̝Oi6Wqܴ=[K*vT̲}#W럼F\Gkkwݧ`VH$$RC[VHQx?ژva.f&aC{7"{2KqHZRѣ@9j:'S1XeF`2+B?~l-^Í@_E(Җb# Jg'(Jd+(Nŋ{Vb @MT% zZ1D[C]<*bH,Qqnz돢&hA\Mve60߷qz:y=6/g a'dCgz Ylhd%sއ_qLeɮsPcxg¶G)L`m;\ёoCDVf E1PqNJ)+ZY{&{aZC3wKUbv^ Yx'D>!LWm Lϰ  5`Y6Zq'D/>J␇ I.؊2 $GϚ],93~2o!TMۻ{bS"cgs< g6M]$FHgYl!)opFQC1ҋOCёA';{c\78W\*ЇnZZմHK$~7s9|GM<*bC,,)V6cWc椚dBM:ddG4FBMǩlQ{h rtQgnec[ WenZy^J Y!p7g^_73(֛53{0NzK^nJ7)ٙfכNgmNѯEuPʫZ.rl)${Dq/WեԸodݴ5SkgΔX4e V5:0uPgSw )yJcuZ9ߐ@|E(UىQ;hcnVQC6J4쵞5dZ1Xߙ."j29 -"4nqA3_clv;='zQ٬oԝKڡf[F3~L{Yӳlǒ]9Y很^KY U͐N[?E{j1sє7DTALafʎO8_ư2,=t-[ ך _Se<JHL_ A=$4h~<r9Ϟ?Kx^:_Lz_ŲQ9>ELu1M#5 FUWLC)jyT!S.;]0 IDAT}:BW8E7 8/!#L(;"< sm;}>3-i}k ;q:^!}i=;ȓ;OӧBjlZk++tu-]]], 'U4&]/Pc &'\b1d?Mq$]7KTKnL@a4ȠA ƜL,(Z&7382+Q a6 9 ALCܯ*̃(ͅMFO#߁&]AH.+ FDttu7P%sjvIMS6e竦:8&  qX"U| Jb]AAAA~3qADAAA Am9Ep{?r!s,;SKewJZ~#_ýkMJYceY:fqU{ny'nlN| >o_anUxA2 Tbң8ꜤOwݨUc^:'~%SF'OsfC_!Ae_ t>ccrQ(ʿ~ƕΘv8Y͘c=}Lo.c׳[.bsr+ȴ /xt[ݚ/-\9N> hy{9dL%IEibSWE~hI+&Yzb/u].~u~_ϤX%EhPrŽMAtn͋39]bGGÜŨ7r%J} >nTmcض? wmK/?nu嫥i~l]p[1'6rG4!T.9S"i7/"!.>[F6+24gV0Ǫp9߈1RW&IThmEbX ,զNz;_a;6ݙRNJyCwNW:TjOsǨJ@byj'l ߷hJjiA\ BzC6jLY sd!qD`u9v;2u#B<`׋Aceb]ZJQ9-SI-̱.ݐ᛼I圱K.t4Ś=PQq)F}|dØv.ep.R`{JXm$Gp4ОF1QэIAbl}?84.tltm1(օ7rMgQU6ty"C7s3|\b\њI>MMNtǧ,{=9> Ous).,$-}#(q"WЭ*"[,ĽxڏkٛL/oTTΦ%:>+D|\AGi<(=#uf)-BgE*[̕6CYw^6pKﯥJźXzfhԻ($ ɁVYҷ SWWlۛ]FXpϞq/xJ?QGmIŊhmp7'(&naù gȍ5H[1FH}93HX8 /eQWdE);aIن})0!!I UqdLƮwyLMiŌk.JCrjؿ3vӛs}q5H~ #^h,߻P!{$)t?>~n 툧{asI,Ŭf3 ̑MJ(#칇G-qHƬ=OgkHBhÀ>+z|Oz_KȰ "EC薫3ܧƘZ<,2z6uꎝ.#>Ig, DҪ?w[q#ȷхN0 ϼ%'/7xT;7uǺ>SwMh4ѡ<

$-2Yu9 I VJKFJنeD,}!7om=J2D}\3mD$ȨZpl% uG/n~v#ukA!0HHGDH<\0m OY~\9s*wGZ#u8ٓ?tHJ?}0φM=3:ŭn%0 fF!i|Q;)wۍ`S3gȞƗ?߼9VH1lHA)3,XFKhW(2LY[ǯ@P'VNIZ%p/QNI['c-&T6oL̎8Z#Ly12b FYC{~J<\iۨ-۬ TpnU!jOXIp\İ^}[^mQ"G5\j'_Rռ. ^>}\IK.s߽ŤE`ePɤD/zyǙHlR9\@" BZEp=\On>ۣ.SxMP#.3 Sʤ9$::sOPk O<~FOO UijUlº>s]ZStrk}{a("nn" {0(K:QADP<̉mH,U蝌hg)NA8_AAAȠʯA^\VDӚVVd]Y'!EAA J,&A\NtT$1h)C 2BS=ez5fp"&  Hy,Qrbs( XeFAAAߎ2# ΄sBBC$mo՘:ujcdd9A# >K.Rf&ҍͥ-ߌv2ldŨXn26cxt Y?׌ 5/$.26IM#Y6++Jl1Uu˰9Gd[wrCs> Q/ڼ62-\9*JqkvT2o"դ%Kbc+ z;= 'QEx$!8} u֥Kt_NС#]Pn]N>>a!}Lu BD~s6=+0d)޽ѿ[ _ד#[T qy ngPWg(I3u` $L]Gj(Zv]q7yy(F7"uLr&N<ņB))csq <7>v?ocn@tk[bJ+9,!h*dRG!W;a%0}Y<=q֌t$x]7:νEHhoDzuhԙ6m[ҩKGtH Lxx-Z6Tro'  O2SF?6y-KZ̃v(mKs(&Nlt`>ZJw`^ӣ_qH D[imEo1m9h3Zq\૥i>ludEmmk\5=L& Zcajc֫qx[Cly|Ӽ΢*׎b}Qlk%[đy;_σzg. Uu*j` "[lh111DF=r,̞λwo$OhA ,^D 9UPM]wd̅`<7vL̎ʍ2UfgƐz1HěGؑe$~rC5oLM)Gڏ8>;ܩ6ee2ln6k(0_{lݒ*eؔNy q!15Պ˰)@ {yٯC8c"6(˷COW'n IL/N#FboLʞjiA\}GZe ©\8a&.лGƤ) ȣ9JG]$_DEEEDDaᄆRL9ʔ.[8q$QQQ~8"_dVuLFݙχРՠ+ ,dfBq3%񭜀KԸ"2F 2qi 'N(vqΟ; d]Zrn#WSqwƒx<ÚV\ AcX>];ȳ%ɯļ|Ukx(ЉÒw c,t9g^&6m 9y;3S'>ƾI \?L#{7Nϧڃi ^MV^,ROB0!!-HA8Z3wL]K9o|+4NkAR:OY2ore:^C,~5X3j\mrr₮`Ig1GZ?C2E7r9@DD$NDxCJll,&CDYR"!&~mp VunrbӍTRBX7JoMC=7`3NDÄSSiH,̱R+BE.L~G? ihˉ$~{V^S=Oy;H|lb81VXT…)ϮuՉ w%AImôN76`E:NBx Z{W,L10rwwf:YͫoRsirV^L ~O׋)xpnoXܱ *o Sr+A62JF9YA4ww jVpdҌwoAll'8.o+ϊ*z Qt%wÖ,i/  ,,0?}bQ3}t>~HXha?W ꩋ >saBu^âAuɝ=l؊ REC.؅%kbKNCCrhǔ82 dZ_kzZ6I:)m,ˢgt䶒wY<,D0KpaFtуq}~?z$?eA|Gqߙxh^!. ŕv3.,HêyPmױ1^]6@ if[0i:Bg(ùfdDžFO+z#[Xns`#܇^yilb>Vm(Ңb~ -QȂF5,UR{RdR ciS09 u1Qj~ fRߓ'i 2J|77n]ZWgۄjhw_%o3inݓY۝),}u -c#Q+BC=nÜAȚf!#G<î! nIFXX+Sf-n޼Ŏ; #4,=A>xCfEITsۧ;7 jLTjaR6|/m9ef6߸?rMޮwaQ]~vJ[^b+c7؍%vc/_b(*tv~JYKQP}}[N9Ι3gK{s)]TWؑ {j9R g;nU޽zQV-~9㹦-Z9F6KmZt?5FF]!EMRtLa( ncLqkgp)@j074= ϛ+nFhED~5%c IDAT$e2IrsL]ǵjnIܚe}Ҕ~Cx>iM$!)?p𼽖w(1oe牘 e \WiN?lu^rd]n]dE̛|J &~{GbKbU[c(C #2"hYp!x B 222' "ُ@(}R[eOXҬ|מP {0*[ROyq78W+ k:cq? Sy@;Ų᭨m4BIdkUeؙwjXc̡8JTJͲ![  vɂ_~Fe)%"Bx"##YhFFFL8 "#%.6Bv&E叹6UaSl!m'f7L5򕡰N<+Kxy\e(k.a+?ɠ.ܳOvҎ8k: ꣤rPqmAt*1892],>Γ\XŦ @[uQkqJM/onML:)%G-iݹ{dz)BL&j%[s$NoKJIwOSޕ/9:m>^u)[L[S{]\9Oyxy9N3dO˭/0<*O縇V>r%f]QsZlEK%=y144dQfݲtU 8v( >h`M]N^ &z$ ,`ƯQt#M cH^$^IR dh<PjhCJA9<ŀ9'LOggـiǡQ1>A P~s`%ۅ$$JKsVyQ2)קcǎܽ{͛7';&AH mhV9CG}HFW$F~kXF8zGRG{cę姱Im03E׸8m]XRNj ^M/j~jtay5֢XiMN%{˜{7/`T.i -Su!\X1֮ (yZsӒg;fb Ύs~ ,AER5oeOSޣh8]P< 1W`LϢ!#fw$A|>43/íwMJcl^^3Q<?hSj|!(h \TtfL^4?,9H9 _h7?ozkG8LC*FvU M pc"cn7Y `A@y?7r{62>$) ȔYw:)Dypnh, Kkkx *`h}t̄O0qJq<ɝl+Q V\F%gW /OQnd~TrvN;}p+NMr跾v E?)Ӳ^T#RwѹMڱg36^G[8t2è<=:| cFG`lgQZ6NR/.;4F{EQ([DMӔw tleuJZҪQC*ऊ|?\nFef1 [#_*soX~C}]&r(4+p]ٗFCQCru4o#r#S~aJʓ{/Qೠ]|,z՚Jش|sۦr?~1挧̗fOwa\Or6d\Em22>}2[Ǝɇ gk7 3.C6ݳV.a@pmfºD(mš3H7>9䷮A[@U6}f_+$# y E`\ WDvtRGۛ[soQ1k4tV8݀y=( xeWx=\GSU4}aZ 4-و]!W3(.2aMQZK'Ԇ;lI5NʬSjb +Bup5 R]X7YR_*Ɯ|2~eiX~z?ATor7V7jږ.ZӵYerAęSԡd=xL[Nd}ˉMp't ;1rYF8VĿRޯInݍ٩i+xZKL~>R/e>d;cS?fe:ܙ ;3A7E 2QnA Qst|Iu,;3ө3>?7sDDJ`@iܸQ&&PG: &Z݄QSDKWtMq<-ZV=R/V> GeEx⫥e6Qr(QDDKF]?0ކpGUΚ Uv^eLq=xKa B';gtc/9Jj~3~QR1G^Nͻ|J7@ 1 iJ{ g0( ] wI_:Je7 62w,8M^6#3N(cZ{X%1,v/+(A6ZJb*]oIy-#mm_VQ)'z/14ܨ͝*sb|1D@  .x>{ :R<51lsjF1MtY _qQcx~/$NaPKW.fc@}5K40V_"07 0믻L3m8KҜ+gk*Hx0*i]ї<܏0vJkrQprcJpg+:@ @ZlƞuIߞPs~6(KtOK%eMw8|4v9GTcS4wO60~Dg8ע⛘A|I5/m&C.-~c%kgbԬ_65t [EaS3TcN\o%kУyVq)?UT$7) |=Ou ztP:% )A.KӦC/,@ @{QǕhxÜ>i^LN85nXnD(=aybĿ rɁR8wUM5oJϚyCD.s7k{(Z{tuV.7m $,@ @ ]-[j:B40J~6,omh!1 ~(OC>7F @ r ^aС(~[݀߶D-;c~r:"8/ų%9ogc\%VfoF2XYXP;<$"@Ay7HIi9Vi׍q9-7MGìבZ-MA)[c'5x"Nrl7|_.^ *TkL]y5X/gjL\l&L,l֟{V*rs+yLqu8j{C6K!ޮAÅ!y{^93 1L|:ە\8pכw3M&uKaeQ|UD\f/[*ڎy{R4fã>߾橭)ncW]${irGA(ZQsna) @{Q%3Y1Θ}ʝmQYTIsq4cfYL$7"y4-M~YۓfC^f3c҈g%~_豱k5ݢi5-$mNioZ{=qN_$@ H܏jaN<_9 E \l}?r?: ߣ"\ s,܏Pei\ $1$[,a>gv©@vOHm[sJ'qɯS>%լͱ*[3<TKkͱBIAJcۄV|ga#'QDx50Ǫ )f>Ε0Ǻrsmw'RL5ا^1$"HыtबԷvоjHtH)m)U.F¹r v 344dlP&*[ <~tH iXYc[%c_睄R['uU>{e'fUvŨ94pCE) o/YS^fLҿH"Kڔ0ǪLUڏҾ u}EM{Jя4}bWT!rĶ;v##hu,A2 M{g@{xC,c%[-çx= )!8hү%GˣՌݏ6Q+t}ԔXdUt /Or:+@^O{}%ϒW hyL3 [ eG~K<+ /?ŴLjSmօzIFt >O[MF "wŲ6+ڏEu2bvj7ؑxG9S(8󧴌kk8;Gt]jP}ʐ0WnI> YՊ'…Żsiyi~MItAH!ESVǸ6+dS}: T*?)ҖZ"Q:IEqoL1J>xO".qFP-Żq[^P@"_ GǔjR}q[| ysi}bpӊkQH◰%5wi3 &ó+~ |u-?z<P.-U]!:#jtar. 7IUD ˎlHPJSgRF5ɥšL̕/0j’ǴHW?P!Ȫ%HF…XoMNgI @c-L , "Gt]7<%3w00ll`Bh\ssZ+F~Fm)"1 "g;p!Ut6JPF8u)exVR/Wr('\45/6V]”@LKTX gQZL:.R/ӛ˓NM\S2)U~R=CB[$ENcļߝ>ߠ,N3_O:D}?W"U)ǭ>P $p-L Ed0>7j\-IV^ @CG'&alw %m9vgټ]#Jv6ΠnY{+&<ݷolj(!‰<)uI<3:jE9&=tnYU|mX@# 呀4}c}A(Dus: I=e萌mw"9|N->53bcA0zlNɄNJp fGu2=CtuVĠPP7su5mKPHпZwK pTM/=fCkPVWc=}*1E P@SDmX0NM\|dRDR[J\wr6 ;DE܉[ ƑZ%>SNb5Ȣi6h/]S-G UDSeЕG!-:C-C6,$9 TZѽCb7GCj/VR"yg$-8QH|(*ʘ4xI^Gq/l$AEحxZwf! $HBy IK =DuFҷp3m|DI8&8Ԥh,V> GKEx0u7Gk9S0Y&+[ Yi ʝWJ0(Zʡ<؋kP0WAX ?2<ď/wE_߳h~H"AEPsn2UߩԾiːLJϐЖQ"R lPc}9&C{̢$Tje@a+'"F&;Eh);I İ\fwx8+M-5m1-Qwx"YIW9y tmJqBP000uOaNv(IUI($٠c  a=g-GqNo[qcSA,_pR%`鄵?ILل]Y0|T bZ$by利Q mhZN%_o!1~ SnWY!N:r/c*/tV~SR[ IDATܛ]^FʑGm8K):R<Ȱ%g~K;/.LnK(RR\|IRnUzckx!gVǼpzhO e0 Xٶ cVw0qJq}Úe,&NSS|[o8(a<Or44õ?^ˑߖ LRVc7L[<k$HByagkh\֒>-:@ R" "su~PKhhQ{Եƾ&~{BE۠@J .kWh-k*~_ ͘qstDMGn([G{Ʀrޮį130a#=qƾ}ĬKT 6`|o6+Ůr[\We^W")RR\dStU\ީ$ɨA7n5ckeMXu,`6}6rj@o`lT*[e+Ҩvz2yJA,XYXPή:x.#]JȦdR88f|OV6hvuj2vDUBCz*M]G~_csZdž(;YFtOK%eMw8/) 6ᡑPp=3vL$''G`Ф)yHԏ-~yOnϤؗNҨfW&$?{}ROP~ȭP(;M)K Szd*`A9zpփ L 4b~.# i\Qz8/pu|fVRAN+4+9}chܤ aaӦS7tumPknݼ Yb JL&ſ9/$*061FM-ݿEe X[Y-\GG 2b@"q/De)"{,7cORV z&B. O\smBP tL׽"ū9 gА4(G:BBBq E̡@ o߼+]ω…d{ "Fk/ufW(PȦ.m'e|WK!Hy!@mq^J8 (]!DWj,`eeQ>H ȭ7+k0  Ei1^1>*iQ $ ,|شsas;̧Y>!@ Ƚe@;QdF*KҘ~ bbjJi zzzzbi]6{ "Jy\,1Jd_(@lW0I>[ҹ(3gɋe@ d;xqTq.Q*3D J0ʗ2{7߰1Ve "@ )KfÅ @ oKfϟYOOOBCCA$,\Z{gGX^^݉жԫ`-՛e_D}kyp5zP;ors޲"gmc]4>͚}hU!^[Ӓ^c@bU$4YZ{33]@ H ehd>c?RܯKf0 m>,{/AE&j|p(6ggqP:D\ci|/b] *5Ys On8OC ؏ܺz[Pfȥ |αuZKt˄2X@ dq<;ʺ)# 9BDXA/s:St0+HjW24SQf;8܊{2Ta%{>+Mwnk>X my%[V.fTj5Krni9|7OM9 39/Lc\K{ khhf3f06wb}fjʘ#y=LoGGx5isӑN_GR<;..ٌrz船z88׺JL' ,ۇϫڞBؕq{i^rVJ}'KGCuN*?)ҖR"1ĺfqkt,ACGV8p+_XO.& 8 gI e5WGf;%7sIIm)Q.r%:5lŒDw:iBX4D&lJ;,nL:b[V6(a`&I?P)HG%瑕k},Ɠz-9Qʔ7E{rZ`%Si]>A_2,߆Kr*I35>#hLNQM1AT4{o|yjIXKN넹Hy6dk Wv,ztwgo" 3ZFԏ&8\):^Ex11u*q_1ILV:iyiLzȡkoJ}ʡ<؛5{5u3Z(\ [\=2UߩԾiːLJϐЖQ"R# 4ʢ +;WX>BE'Ԣ8:(nԐN ˶e[[ĆrfmoZá( ś32}JeӦU)+`6(PeD3e'?RgQD"I?PIHG% t)u V`Lq>@.LZAʵ@R.yF8=_uilC< ڣk#C{]lwtF-ņ64¡>iФRGN='8Ma"4W\n&I?Pѕ)Q3IO: 3ȟbw(ahjlG3@J6by Ȝf`dG X ?&2=&~d?'?o.G?geOi,T .\9{ȌLf)m)U.$) ȔYw:)Dypnh, KkkxA *`h}t̄O0qJq<ɝl+Q V\F%؋2@ִeɅ3}% 9pⷑL\2vg+G`8J%1o}~lomk9e,&,?u'lF=(u%gp5O `A@y?7r'1n6at3Iԟ _qYK:б_]JwBBv4g87!FX0n-@ƪ=6;09]Cvlt1g1fYKG*/IİVe8LHdfu+ht( t(X.bSV@xp}8LIys/E\]|,z՚Jش|s2}ہlØs|+buP̘Y? aar?Qr MUM[%%Zg^&랳_-L -EɬL;2'6lA ޼S̸u0vZc7܏k8p63ax"6P˙ zd Me&9䷮A[݆-:XXqs#3-KlIMfc5ėM*@+-rUW tJbHU=逃T׸=KZveվBJףȩTN[?z~bcO/)Ct5cA۴'￯ax yHԏ-̞4`K ,uԨf%ԉ%^U42sF4ݐa=&OmH`Xݬٹڗx}6fZUJ{$x~dc Ҍ5|>"}{"IP~ȭP(ۦm~jף:_Y*2]h=뎌6# i\q8o>Z~sjYj$ߎCVז rE<>\{^0I`tꆮ'KSqj$ٱ|͇338v3>\N2RH,kʴDyqr9ϟqɎݿEGWWju}Io`^Zx$~.ң=+YSDkm ,FM\A ~\߾+U+2΅@{0inYVesƬmΘ+EdbS# K? Rm59 tCbYS#4_i&G聯Y8'.aģCri=n;YAʫoKu(^w8p@ rbS\+Cr'T2 ';B 4G';;AWO}=P }x6oӹ2 @ "ݘ8u4n# @ @ s@ @  p4ʲ_ftJ.㻇R&ʥ0a5HnQrK>pyLW;kc3;ȴ69/kvtE²#@ r/Z@hَFm](V33W?(&3y{+.d/M)dA![瘿9΢ l*ΥN,ʙaeuQD:aܧ et4c}4Ik$6%wQ6NX y @ |H㥴 IDATi\}:OU8mkU}lKYcU:]gy,"8ZceYv7!FAп\ k s+7gvw"UiWWTძ %QmB+0Ƒv(BqMKǂqSI%P8|"AJuv'៸tMy sT<|4A*"-g^[fmI1)gYt)}0Vp&Ņʠ3\͊fdP&J W#Swe[%iT׃Y=|L3Ȉ>AgAȳ}^ -u+<4=?11a+Kxy\e(k.a5>(*_rt֝˱~<{d6AUǍݟԸ5AϱϚs9h:CV?C䄨{58%F妗7v&nu_& !ޤmV2$ ns<>_XMȕ up)`3[^k``H;PkiʥvL+yj)>A&V_'e1?IyZ^8V~YJ<5^dЅޑF] _ Nuh6_~;ǗvDVj}ԔhM:@ ȳ<˖q3ŨM~K<=;VZAY~i3c]tօ.8$%x3(:ǃ~&#g;fb O\~4.U(OwU_w{c0BSiD "H J`tc Xs={?ox5s{ V/U #^ x}t{—/T$qz[>hG.ggrqU7~|6MLAhң&=q(B^STӟwPVzH1zZ|[ܱ:(3;w} ܯD7,pWpN-O=*O44a kH-•W~(xW{wGm7+~Qt^3g[> /@2֙c`E߰"" 9Ϯٗ߱ztïtBzBҩ,z|JyOlI)a5Ͼ٘i&ůiˎ4,5ƿBIG 2&'=.7S 1哶Ҿ)=oٹ#azh &c,nP4Nu~w:2z78% [P΍\ 5Cf $ܔ'yV]1!\JlInVl48#o&|r^?)}Tۙ}Z^reV@ɓmԀ5/ zחhg!FTrJԈH<{+wh=y6MM5ζ{3 ZϸYG ưν+C۞ΕN$Ӳ3$p眸pcd]?L/0reHyJ-ҍ*2vCtA;XoL-+&v@\vDq(A!u6{ ]YfO}t.FX-DLpm .KdIὮ1 VUS ^f+ ;j] щH31v8slDTN6Ķ&w:KS'TFSIgHxř 92r{H{s';3 :KΜKӵO!6B^DZJFVY ""YymY)/gbAkџy!N>:`ݝs-Xn=lЅuQ}~k _Q|ʷc쾊|4)+YQf([?X[y/OX0a͡Hgm eM%)QVCդɔΟIzԊky4\J ;5V!7s)8 )v_8p%:f9$"nFuXu6Hùӧ,cNXl6p ]{fmW08rQ 8#bw*"""""Os,'oER8ǹN8k}#:ݴv5u5M޽QSzXD3DQt5/o]L쉦uo<QXR퇼;jΎ+By!VU& PД t/L7WҡΏ5'::ӨyX<$G*תϳgR1aoH"62ADDDDDv ]H4dA?~+8Mhhu[]) et)B;:OsE2MI/]SS!;Ks)̟5[HSB$SE4+{ZKDDDDtNuS<Ey"l.s\"M-OEY dGd&l&:*Ȉ Ȉ'8uPO|DD>KuDDDD$ˈ4eNqoЅ)ej#9|żyoЅNuN~5y$#Ű2(u#?dLڌQc- m0;m?8'>@7#*зw|7ƏD4|H:U}d6jwunjl EDDDDam3;DdTUDTUMfDDDDDDD^B$d*we"~H.R_Tiڝѿ%4#>I,Q\ɗ_>N$e<KK_,?znN>H>p!*/lY V.`ۍL 5Zt"0:rEDED&yBvXTAo0ߓsEe:t'cޝém=g,q.we GTm|={Bbd'q-$FYg}eJ?wnZa\>> t^DD2YV9HlT-IJNiGա(cLօr֛p=rcIaTx̜[ e)>4~c:`R TncX>’|ʾڹ=EmMyQ'~?D8`;gca \ILi|Ҭ. B$NǏ2ey"[U|!y㐻(am /g/S 篿tyo?O>C8 c,xjdu^z)g$b\6[WyGĕ]5Ut#;v3T\]:6;˯XB,ddTׯ#!U*eeB/y{~Ԝ<φ@UJEΕj;bR+/kWlL4a̵M|={pMS@,lpyXs~y~{CƯ2vInr;gޛT$ 2;Igm6!2 dK<%6syD~5u5Wۻ;)wl̬x3mMM,7w {Y 8!И+=#Y8Ǐ{,skS,hǫdɞi ~oNp;cZ6DӇ7^'ֽ ?T!2}eblW $ٶ ?ӯׄ9s>CJ~J{rr{7@qQ7KK%mZ+7h?h>`)}j3kxMݢ X8 {P#-XC߱q>bv""xYq~>/~:eir$-ףvd\@#ӈHq?&+6Fpa/>;ADklbLgh2us-e[odu뛘08uvc ?˅Z0U)r:%Z]Xw4/G>WGT֯ܠLIš+""<<_|.v)EFQӚ0^ڧA-Sg?١H*Nҹ*[.COzYfU5k*]WvLX؛cمY֕us`E<,0nk ~3//7nbkS ygZ/r)s ~UhY?n((s?NB1^!/nLH%$KIO7x3jl'䬺OP^ &aV/R֗8%kk?crfBVGlϗR<\[ۋ-/#t.Gu#@ N]#'2jFDED$9Ϯ=燖9c_3脒&9s♪?zrIDɞ3&*TG'{JI41zzPC–?IN;boԯSmtg|;܌4=y1EN*/dDVӞ2f`eN^ ߷[3&kNyi$.P3̟5kJ'vӥќ4S"P [2nxPVzH=|M^ٓiij`J.pB͐h2^#”7ki֯:_yZ5 wkw0dW72m,ە(5""y:φpRNUףfe˞5kgv"Y,Ox 3FjǤQgh1N? nX(Dv.p ZXCp΁$x|i#z~[?aӼqaaL婸!Rzn ,cSaϠEѼЃ9O1pȒލ0.'_9 e$.&~|W?Tgrl8x&0^sנUI٪]toL?a|W)}8y>C;07""i:Ϻ%K&ƚQ+DD$Ԃݡ8W'L UB{uR"oFq&} jL=Is:>7HG2$ӰVlʰ8b0'ACD 4_$)PL>4-gIŜOCY=7:-G=w7',c}rI^k)}yGA|B_xZJFy3KDD>+ϳl)R>pLaףV]3HZ)!ogīEhA?yh8!b#EȑՓ< 4M:h>=޳E Eá/15.=03a8uc1%=γDĿtfLZDgwoRֈiCzm?ޛ!Dg#؃)I {kKè{h>ĩ[DggpT$ Ϯ9~ ;{}ڂN~dd؍Rj.#"5YuFa7w͹K2x$;R;'QkDD$͔0jJݱ{S>ATv;9c?; +QB}zN?Iqn 1ȆwS'(><Ϣ,k|71A )[CN8[9=>wiVoZe+=*2rsOK ΐ tL* FStXӊGjxt֔C^|LP,v` 3:2dyGoĵCZECX42>Wg([2 _NdOHabx{_L?_~\\8zu7x?шNΎ88eţΗds,QS'GGSp=<qd"({xPp^͐cV'+11^JQBDDDDDDDDl""""""""bsB|*Ց?Ll_b}HfŚAmMQG\oY KY~^xמıGPBDDDDDDIqgN6u;Xv/LnXnss%(!""""""D#8sgΟ>6-DײF F-B a=?3=phێ[f3mcHv IDAT$(Y"""""""OFՙA)[ ?NghT,zMggF.G|o¼01 ʏ%跗Ļ4NFC=^ZT.IѸ&;4[SY QRlRb =P2ܢSVs7u&A^نmoci z;s "aJ$?#(݉EC"oRTV݀?ք&gӿūQ"ehVc٨,ż<.^]; nc[/Ѱ8UūYr,4|ZlQuDDDDDDF8Ϻs*wV]7b%yi U† ^zw'~([eYDd/k5LGjDxƟȃO|PIcCxX]A}~46ߘUط_歿HW c̥sUF~w^mJaT-L938s '›P-3a}~4H6(=r(H)nV )?j;8M3~U]g乙}p#(`qZ""""""O*`-C Bm| }~̵Sr.K.%~Y٫dֶ|/p5"' eWx$ރـgumr&;YGg'{%p+>kg9ۍzt>kXppMw';pGD:ǃHԕ\pϏ=$)bJpx,?sv| S$ϲQQ6o``Ʊr}R)-3kG,%s {=w y/_ˑPWGѶYrPGFف@~;9ʤ#+BL,ww\bu}gLe6]:'X8W,$\r|iQ87CƳhHn[!_qpނ% Y߬tp4 NlؓpMl) o/f!:,`#n [Bƨ3A3藌ҧE7H.Z͒]6;Tm3;5G|J=n#SzmdJ jRqOSƭČ7ZܭtbO.xjvi6y-M{R6]3f [4rgU1]eKtȡ+07$cF)$SBDDDDDD(l"nk;r "O?%DDDDDDD>džuw/&[A(!""""""|i3knU2qzFX^xsC}1 s}'E˷`χ@j<)h~ׯЭ$Cͽ8 y剷W)<7i0QBDDDDDD S..u{z<ۭi[~x.<=';Q_tg >]_7&,9ol L=x13oo:38ѿhG.ggrqUX期6=! bXB)ǩ9quǽ=hĔ0ZePMQBDDDDDDe><_5;=mqL0#Ev^t<xP_DK>-1?M4g7 bpbCL7ݗ“%ƇO""""""w1Nj>OqύcBzǕFLmVlbB <ExBCmF/}1M`_,72iYj)X~3'D:W}R8El"""""""o! Gg R'u2_$^ vnWKѥ=3 jθYG7fa{W5Wu?0`*<#?7plhr"||4Nۡ&3""""""YoZੱ$]e3DqfB5TƾYJ2PCrm~ۻ.S͞9Cý\-¸D?"t1nQ$%dPeHc08Y'dt>8E~JuGT.c1j2#"""""""6G 9JͱÒ!<>,!""""""""G 9JQBDDDDDDDDl""""""""bs'hCLNe^b}H&`$qFsnh뉷'^7"Y1:XS)~Czvq"O'ݡ9Z꥕ QBDDDDDD$fܪse\މ%/x&<.Vۖ$imR^-߂!?"QǧҪX_*CƒL 174,̥uȴOs~VN~=~e\FD br+Yd̴p" GcFaiӽ9̦GMfhꞡ T$KSBDDDDDD ۗ^}%< &[& LQ8yVL;qߋ b8-#39uf츪r W,yiFsOƞ5K,! odb8؎g yh4bQ -2O떆8E$; z؞;YUc?ڃhS:RmG|uqMЪQxVp/ׄB" \oU@i93o4ugp%'sbڸyNlr[<`Q B/UmB~Պ4?}!}Q]~ޝ|0.'GEmF|,g_HwnA\?D\7DptֹweHý\-¸D?"t1nQ$%dPeHc08Y'dtZ{snjI"B y#e~ "6FMfDDDDDDD(!""""""""6G 9JM1`D$ 2f(!""""""d\CDp?&ׇO(!""""""6ŷL9. Df""8yKRl )_Mə+Tѣ\*L&g"8G#6VMETj899,Qt(yqtTmvIḦQBD[DlE~E~IdY8xlR<ٴ<<˓J ɲL&65ȓMOO<I"%48T-"O6"O?"O?DmW5DDDDDDDD<"os"# `Azի^WUzk2XɑbEnUzMwiL"!\pwqJw@"""""$"-*dƊ ,#΍+Qi ɍ{1aЗK%Y[ r/L9bw1SڼӅΉ>eA_P/Wa >].%|Bׂ9y2{hY db}-G.߄myG֬GTwf}lED$D~dCVK:Fdv uOJ o) dʠ+slB!/R35`sKPjf qә*2&tP9(R%opq'&S/, d6y [ SkjЁ,e1ɕezIpdYgQ,D^ɢ_V5"1ͳ %2"bnc)ŀ;ejв}[jz:cs#G䊑r&fUxw,9C99o[~ܖBzKWy?#N=)[uPxƟyŒUjf=y+ n$b϶tBsPzk^X"ΆO~ԁ[~]gmȎobU\`Y#Xw}~jy([秅byQWV gC*aYZP wI> sJYxx8QӦ?,܌2ᗸZH)V_Ud;~M]8ۧ&vy:)Ԋ?9LfyZĆfӒl"'r:-ҲB>rLr~{~'i^}! >6WG|2; F}#]ƁkVJ dy 8'y{\qFf#YT 5Q(n7nay? Y=g矰opzf:ƵoӮH^j5fܞOE IDAT3޸[މ=&n<(ܨqN3&LߏuC.phq\;ZIң^Jꛇɫ%Fu%,yRܽŮKq܅Q=q=ƚof gyN| SqV3k 3]!@dr>  y1\_Lп{ϙ7.7ɛ'/_¤S)VO=DMqqq͘{~?v]b]%2` `LYq[XF/Sx ' {' k?۬UD|; 4=f,v:9Ҳ;1m}'cWԮ![3o bڋz'9NTiտ D5=tzxLWӽ\!ܹͅɄ8G 9p+[qt*E37-?g-TR)z?To̞/{ah9 V~ۿ3[gtyu\7I)Vtv7vYdo9[ADP/} s?\{>yO<nNߑ 7< `]YЭLhc lY&ezנgϒ9C`j/\{4\zeAU;S9rS[ JϢAM/s82 ES,'t(90.ハ] 5}M^.08DqsBkaPR2Bn`}8mt7.ӫN!MM(\׏aD%߅?5>i>u-/.Ӱi<"02@z=,M"'ʀԊ2JccffMvlp $:3dg'AS!ȭ=ԕH,Mw!/p'q/_*Xb$3ʥy|=x@ LPtI?d=X&1rV|$`RsVsVpiMVȰ/[]kI&;.cUw%؃UT>]Iw/>A6T,blZu ͫx%/Ȋjdd6/d^CgO~8ձ+Ǘ*dxˋL'Aƈ-3{q`YTG&B2H/1L8O@"  q3=Get)t F?H10ˋg %~[B[$k1u^Pׁ6sƗJL'ASf`O~I3|+dĉ!{+{5L>#b^^gZL8Q)yśRҮVQ>MYqeg\f\7Y ,WL\<)'?GO>%4&5` 3V Ӽs!>DA(Qȥ=x^:E"Ʃ0GiP z j%}zE˖X[Zуtgw=6Vr&7X%-d^cEҖ)MosH<R˶rWgs Nu cwJ+gx{ʐS$en&yG:'F=^sv8*?jo_ C@op%ue@ E.Q@:xLFZ gwka>V;}9W& :3 Ej`[݊P=S'y|s~B]a22Gj4/e9S"`:{j􏣪 <]dyˉ$eF/s! ̫V]lJ(Kkk &L;)};Sb>=̂S TZ2lףȆܾhgj{)F%W9 2+& $H0jF <C*bZS{l[uOy2ƻ2;7RoRrN :Kʋ7bMҟ*9P| m6ݤIGgҞ"Q/e@W ]ߴV>³MR`ZE&-@ H{+[iZbO\^ nT*oBMQ23 )WՇK\\;GgܼeF |fMOhL+{u}p_sy. W @yPM֭eQaT-G6Gj |i@ Ј @g$8Ulm>$Ak./_s@  Ė@ @ _zR6@  +( voݘa8D[ |( vn^eBd߶M۵@ @Eı@eBo gwnLVuOs/yX8@ L,1 @ FEHɹ< 3Cay"@ 2W04RE\y̽q1H.E΅36@ 0KɟLj"bAAxZ^CZr**Lzԯ6k?.f-3U4׽tٰ‘g2hB˘68FI/jSW׆Zc+[j_SW3gq#J:J| c8U&DBBB8v.wS5gOJ=\LiϋGPW\Rƍj  2:ʠ ʩ5h+5KѡX6u*/Gnτ\̹O)u { [d,Zi&y5*QA:ȗ !QkP4L( -ݟT'LbCJ{,j?\XHQ9{$;lK*u,\.قXIdj_ū~{ep:WÎ\_ǂ| -2.nݼ'}jq@8:8Ԕ7nPFMǟk'D[|΅c(8 ZCV5!DP2肮r;=7 \MCD{ i1;dK}Fu!lXFwVFmh![,e4~kiDȱ?Yl#7Fթ'̟N+~5}bv]`Ce _!mܱ@J),C*U<H2=Kj |ә0,Vp﬚ B`zRCmq _<6aT@}l<ЙM  n*eZL*C?8z/K^SQ/yq ZĽ(jh41g 0qĭ| Ost?5hQG\mN@I9jT!9ƱR|&Ά`;(,\޿8l@VoJ>3 G 1w'Čӧ<˦i"TQ$#I(Ӳ ;_$U~珬5Gvp?z6P2 7ө}l*uz$\um;͈8;PtR Byqz)~[]t`D>aRXɜa*cs@咋-D:B`n|sRj4(op1V2UDw:]bZ15*2oD}=j%tڼa)OHŎVmn)re(8l6R JE2bq'6%O}R \}zAQN]eˌ zQE{J͏As?sM 6)֠A\׼\SF&,s{Η`,~s sRdTex碟@"yQ5"qc?~Da\5'5BôFڦs 7ϯ /n0H'LIjX@Pl R[|vH=zѹNf}Mv7B|aݝ7N:X/( ά8̀m4)?m~Yqƥ[Gk7X~10.(x@ CNܺY.uPi>t48‹J,Qk4w)F{E݀5|ɷ(jU=wNЫ&ׁ֯B=m׊;{3GJpޤ#*AѤA7^q3/N}/{Qͺs:tBY'~ԀF79կiY0v^SxRn6gy/܎6?PsjZft:"ʃ Ǒ!ʑIK*uuEJuh;c787(c2S RHEQEgdA:[AuմvL2Tn[ ڽ6 b?*/ԳгCsOa{" dyڈ}K ~:"ӡhoΤ o)?D|e|[f"xx6yjNDh&+1(o(͓&A@2S`mf]j}B|%Q-O+oW/6i;Tʛ؉KE@ܔ3,>7Ooi/'3llctScl& 4ut oNapvyAwԝajGS{C b7VAi,1賌VUѤwl|&X`eHG7^8>e2"ןQ}l%MݾǶ\A3^l( TΔe#"Ż׭ΰ"%{ʸ4 ar#yۦ "0r4(5t"`=}R'.ֺ"پ(| yȴ h>ě+xa!| *ϗnށG2~1C㬚1)v#q]>QfYŹU$Nn I׹xdž?84(@Q`xFx^< +MF(șmRw*t‘frRc.o cP WRAV*cU2|ܘ7p107QNce[PI詭k\$M.s7SUQ5* TF+RG[ ݗѼnNNSsя7 N){}[rh4j4HШ&4qTTVh /_NQxȔTo:`k>|$:HNUKQ)w+_.Qa>EE:WAjy9b&u(ww^Ĭ% Q/}rt*vM%^O%Z-ob>hY a˼HC]!V Ȑ\{N2ylV/;ߔݣ=q5BEa&MNH10g4f<|Suz2DǺQ?o<ABE̛ؾ05ȄOl;GEBItT3Ib؈,hݝiǧ.ij?:sdXG֮g!Dũͳ\ٹWqӎ;s4G&J6 Fq0Ѓ~boo:Mza`sU'w"DF[s^?e!MB.w9B#u.=dt+>y|bLRwRt}ie4S)`^4=n .*E/(ܡ&S{gʣ+Auw'F 2j5co:2xz-w~{PnfyF\jGCZY}cy^<&ih"oTpݵ-FmqvdJMߺGɆ _y|%>@HMnw` [t7IVTDeŏM(w [$qEіI-wéh·oOmQ_騇۬K.F~&d*ϨqmP$ ei9b%7"V@*M K U)c3/d2Ȼ,|zsQ{Tk9"1?pKgS,y/Qlp17xe55ܾ<XbY;~/Eܾ4^ågO_KClSf{D@b{t,]5GaToZ>䶟YMp*ۄ:R;mu} VMVNp+F4vrbr6nɛh ȶ%kQoJ{<5b hγs!ĩLbqZ,_C9ĵ/F?2,+nB_oNɏ^'xD[oC*#%ɴAG+mGLCE{\c?!2H%odXw[,#4N FYӛYP#ĵ*e,vi˄6'y>4edAaMe$)]h2,q+} gᇻ4i 9q#ZjqխZ?M'o_=桧X0;ÌsvN*E)H50@Ytp*6p'mwF9gN}۴Wuן0o6 f~ԝFX-aq!gco<{&2*[ Iŧ?3S%}VO1IEY4`ݭio]Y3pj9] cc }1?\FKqYOhni:}2{V{]:a}u{2vO Y: uNS>g]>l,6PUDwcú\690<{[*[T*<~.=pIԽ(Ӭu{ Rߡ[7gʩ osp0#djG5\RmM?-+օK2ZZ6@ ۨX.0CRc.3`dWC6dP2dJ]}6ڎ[jCӭٻ=ZH/@Dpſ%-Uؘ~jY5{|ߺ'~d ysWROzzAv+‡Tk81=Xy`86ЮYFO u}uUլHB(V2弭?|gҗCJ=&f)6pM}D R6țgCͲaTUEA4ËGPTs5>X%qB0w!F먳4yUՑ-mܡP(8q 4NӬ3 )WՇK\D&;-DdR 0t+BA [ɃG8w51MP/"H0!/ d^0)NtZpAskGh8`'7Pzĉ.4f_qcUL[)ɀص<&hR+a T@CܵDuq~qDZ]qJ7 i:&2µpy8(h{l5y[A¯ԇwğBL!Vkc9~&fɸo(ҋY ~s:͒@'g_?wC`GONUe5I{3fq]~f4kFK0V ,gP_9|R?2_;[y@& }Gt#v5bw )Ĥ@  4Pi4XdTt1 /˾5$CoRuoLH9$[8`JtfdmˤS-C .SƢ3?C⓾L|ZuX5q??.D qN>] σsr?%_UͨmrInL*Zd- ;L`\t]ֵEuͻ,- oLfЩ(d)U25CX.s۫){DDf@ HҀ:n콪߸vP{~k;ƒGk_j%JͰ6!(B ʈ b&lSK 15P+0’RHkq3EO9eu$l>aۇHkOBSEbJNŦ3!sp6aY`E@x*e%ܟpf~<0,B sS!OFQ5 LZ1a]+~{Ë{7v$lG]3=99y^&DbcP2q1:M(@ |(u'9eGm:e+~']_d]Q|r'04r N܍~,7NNjmEB/Yy<aFIJ\{DÝxdwktEaʘUS(v11;o9ȝT!޽qk~1mq4uIfMM_gQ2ŞI goX-NL-=eҠ:LbQ領asPΟO%HKk&J+h/ s6j|s^R{T?תc(<8cEcXJ3OTglu @ x> 9VߍFDN(MUi7z-"/+ >5)Oaoo Wh5@9lE)Tm$6齇#>2TDgX_*y{SDuڏ]H 퍵uR*Zp3`# WBNU'CPS9w"X@ |h> A'wT9W_D4΋7vf gXm~>C8`_?P,ï{u.~ݙK˝3dҕ-$A dI;f8۷r~[K?9nu_u?2&bs9iI""72mkAlA0s"u`i)~å 䅩=lremopq4igx.w }zM\}&l`dɫL~B\WC)ؽ!WY*@ re*3;)ҳt–M'~[@TLE0W*δ49x5goh4uKHlvLUT߇R)TZ?Qi"CsǜI/O~JEz"џ\lG޴܎:]1y1c-[f?E\o-iiEg_o2wZ3iY%dz߳ gR?qϹ2Kې$b8. #1/Җ$a5hMȔ0_dd5'Łű%2q@;Ytu]E\Kl}k_E0nv{.l[K,$v`=h/qxy{߿0rKΘWY. yȬ:7gKleF-õ>e^~vwcP '~[ju OSKG?AXlSvv),D@𕑣"u9v=[p֚`QNy {q/7~ayn45)esfn˘Wg(ްaiSkc9dx IDAT?,X;uW(|)0]-U)6N`q)v.GL*QӢ.+\xP }EIΧЏ-Vxڦ bܽ;UʋMXveY y{S8v<&A-1%Ժ , 0pt,_E3;OD&|AR`ʘep☂nu.u Щ}ϜiX.+r7)o),L˚/I~HCm^f"h}j/W,./Ru4^bp'锸Ru_N@ Q !67u*h3jZa廟Uc["< j@Bsoo5yCϩ8 7"}N?d)Q"4nCŰYlECcړc& /< QPCݐ 77=K ̽e)w).l24JS^s>2uN0%F5?9fTXі|23lmSmA ۪Mp Obg)1rcy.u.u Х팋M vľMjEZHb`z}df]w2@FtdBj|h~[:s۸ rfJmk g:XPOfCƽJy~fYCؔΦ'ԏQ~:rҞqa6OW+. )4hB#J=y,NOY>R*B ر΢e:1:Xdv{ߟ}S⟿**bUR ue wmċE_dy]C[.룟 sj#ί>M$5\Q1(\Lԝ]\OM$y>Ƌ1vy#("s~8Ɲݖ Ft$Lz(%`Xԁ~zAM!sq=rƢ :7OU69Np; 89r#P$;H"N0h:ǃUJnsxP͊8Kbfvb1gڶ0xHB]d-y.@2:'3nw>Dd\_?ב(TJbBsa ;I)ΈKяԚ2z?/edxMwYB)4 '˾-mx@[josV&X}5ХNԥd3鷝YUG x Ak[VEVt11501A@r?{{9=3sޙyy ?>>=P)@w iAn딠YW,ÃH}v+C4Ud;؎GUAR_f}&? T,Ҭw1p9Ӄ C{?gպtO2P`[Ϣ$3D_#I5L$υꯥb ~!r@ I5dƮԖe&EE =˪4dÀ2?ctGcHɪMd*ŋY>4vjRVl]6g'0v05I" Mtݪ[23݄'|Q=ΠZb.37iq4ʨĂL:Q9,d;T)P{:ⶨ'G|F:jaVk%*.]P30nvfy2cZ \Gt["5)jSϽ?]FTb,N*A:Lj^I^o1qvg.`%W[#HqJjRW[W(.ZO. 7eۆ l}QY0<{H ry\VLWֻ\D&K|8}[FC#N=CU#0#06j Af#3;Q>Sb Em\X={蘇0 rarEk:]( Ӊ.,C/D;-m5Xg/WR gNbߤykwoORgjhhRFM6YqW1*R$oSSgd252kpt= 8Z2 UGCA"S{SHCy|j GŬG{ c ")ɲ.VA]{xlEg gÆ4Z7@bDYI[%'pbOKРEL-7<)OoT@ Hӽ1s}Q.lf8-txq/<wq[1 G۰fXYRa/f!2w\<D({FOU:x"6םtRNE  @ 9$rH\r9#*yj9-ǧLFn 2iǾ^lbo/+۵{N—qV63~I gV<uOQ[/9c~ 2eS,`o"{P${΁FdZzR),:t,OdXySS"чƶ3˾GU3c;*Hʭ.1D;<N6hk6A,M'"nMɮr @ tS?%W~IOF-j .6y-x1 Ւ -߃*%Kt9\V "@ Vh=K<8tԇx.zݘߣE)RCDVuЉHXkU Z$< RH1EmDp%3x&NE%w"P =)8,A)gp0 Lz8<#O)tw?FǮhZd"(FuQQ= MbN_ywҔ$Hl/kTo[.e47>=85-)ecϷdl\f '~ؚ,k+G 3Slwf'*RYf Ny3VȨd)2vw/O#{$6^)KLo6I|y7|2l1u<%Vy$C5O\bK+(ɗZBv0^x캆Wx?:φu ^ՅNS'#\Y4B_ wT[k,qck9G2m'$ ߝ^ǜ[ k2S3 gs|ʈd-Eh3s,[DzQ\lϿLXvg÷Lt YrbD#Vt]5쿵TՇZ*Fm7H@r.ϼ6ZfD\psqu[({σMni#!7X]FvSIc:Qu! NQͭYv4kf czOtd>Td|!_w7c9MXQh‰y0&l{$9LE<)Y;.#hv#ԦvzI`_4 Ul;#Q3osh6$Zc?sK-*Fb8K 9<\x|FR|e~CL';0iuI,3IF 8WLL9n֙0n- J}dvLoK:ti*]^c uH ~u0Ç '0˞s,D8ߞF)e2z $5 t Nos{EØa?PB8/WX& A3`/ <d4vH5 sOUQ"zj$aV7G]r Tb[͹\\^{Z0gh'l9I'q)2lwD#r:XD>NM#mXk3S,,ذ$.֓iTs*YbQ2u$(BelKXba^6N~[bZ0klׂfXXW-< @zoW)*fF)YAo P_V0U*D8 <vO;B͒XFRmn\OtҠZ.;e2%fY'xfyXCCCVO'n{Cɞ*,m{굗98/֬e>XkCRY1hC %#_q[K58{E*5IaT7YyO}b A h{v/wqbk󑿶J3XU%u cl)8}BYY$3+pӁ𨭗9<=(̝î|!C /}+Fe %phLI43>gy>pdjy}˞?݂c5Uȧ׿X[0-w*;ie+\W{ygAz4tRǤ>^tO"Y9U!OL܂Y[mD8Πt,~ۄmo2Ft3 .}CoP|bBl`n$O;x/=gl#ȕLnSBZZ*Zz}fp%8` O9Ă~P$h0|\=WV_Śu9n{mnh8@xp)fXD#ql j<&OV6e~X:eeDpߘnR+BL?c˽0a G8]OϢG빒=z,}}0f]BbpL]JJJ³;uvƿlJYwQN 2U%N@N.88H0Yl‰xpy0c9 DbV.ؑ=ұٖB}8 Z16E ~Y>Ч Y*ȁpo?(LfBj`ެ#%c&ߦ=(֫@ _eU)'J/vIJ Fo ˋ^uu=bJL`cv׳dV*5S>у.q!՟ݣc'& Wye,VsBpIU^84z*r)lY'yE2zeUy^ LC]`Zא1{?Mse*eI|fkJ/4c+R529|1}^ ?LN\n%qj0V``P5 0Ն327fb_hiD`)C32O& u]#t">~BU|q6_WE KM2Q|:;nܩfcL~ T?S3ȃ סADH?n7.!JwP^@-3IZt ji%X_qYUQܮb |GաDld@mvLv'sbTN4zˤrf 2-V1m?s- l$}NR E U$i{AOs*Iz| \c8;t c c[̀M9{}{yi2ML·h\K~$KJ}V1j AӪ r rU4cpsk5˼e1xm]]<9k"D;eAzA "q#_3Mcm;Q)@]Ah/x,Eõp\1[Ti0;2}BHCy|n#9q1$1/u90&VlDe9a~~_ˉxmF 9ǸnY*ށ|K }L IDATKef0]8f✚ЮJe;ecXԺ#Ƴ Bɑ ! lSm daAx_!׵3bLyz9 ^ K ;];tXQhpe*^o6`z0"Eϝ]7 Ƿ4-,6>ƤY f{%Hnet ID͐e z.T435Û`KѲK܉>\Y9KU Rϩ?j{3auBHCpcLP4 "M}o}eU YN6ejxphZg SiOeیͳ0nMH]ڑ 1c G&F[{X*}߷);6{2ƨYj'< T,ֲWt1SV_Bj UyS>@EbJ:mZH!nqJjKWKF:&2X 9^v7+bGބ5$֔ј zPWEGQzh1p+*A8 ʰOYz= 1]sƱiF'dgR`A&d JlPs'dSffTʤKf#Ԍh9)/:өXŦ(s8T*™7iZr:SCCJ5ji*,-,PWZI62 Wd2Ղ7?|V0x)>d9=u |lqW_CA"΃y1R!xtbCcާ1dﶯ4^^CCvW@ $p*h!Y-Fp-^jA [^M3s8y& u Zפdj6QRމ)@ dƫlJ< !Oy) \Ʃ񺾗Xṃ+|2pV\zckZyJ[G:˒ɧ6dӭ4HDx5+L]Б*;kbi-::RAWŰyaA1W}RiE/G}왏h8}eunqQdA42>;gn,ZhEsvD@EC]D ܀i\io*!@ %Dd9K捾ELZ֔(]Na~A3EeJZ[SJo}H=]HO*M}f7ӥ` 'uIR*`i4N$WPr֔(]N[y2քJ9-jWeITaƛh[umǙ6fO(L1c"!uۉTT@ Yeγ\U(b**1Aӹ\y2ΓJ|=V`l Z]+t8M]P f7N'm 4JjB ]Xғ]-HFwǠ@ 'D^x+3ކ`F\q oOKzSpZ4-!ÕC [RK)m@ z8zD_|>_++ZWI/C4 |Tb3QxӤI,ˏ ԧ^E[(;b)wI1vT! =dn肵r?C#Z76w&fS"%eQ7<2}" ^79_ Bt}<]DRIbHEeq~/KrG-4<ց1WͮŴND`RۊrgďBh#?Ş\u"}@J`@  pufӐKxjъRO/e"䋹,!()bn* MiXErBT%̗.]LBM+-qy3 5#lWN?ظ S)gFKgn:Jپh t["P`t{0) ;sŨX{s%hB /KP,ӐnC6y{w)[{:(VCvt|E*$1𻾷r0+{a 3WsX+2z~l숱P-/y"qKID5e r;*Z題VF @ ɐ!яI0% d_zBpپITгDBm\Zcb,NIa9 ]5[313~pN.N#3طʩ&@;w_K@pF!:528W/`NX>K) k@Z+g}RxIFrTc7{o|6Hf_O'.Qx@m<SŨWo[yxG% WrxNӸ!s"^"Pߎ"yMi >-~"m(STj@ A#|wiRI5 Gw4&jvqۼwG(0CdTvA)ddjIcf"8,5rZѰ;UʛS{yR!Y'?Rw;'ZDx)}2jFt2Odk]<8$]%fL97w3*~ŻHڞ |\Te|?B'^bEQ5#j4+č-W{C.o~'ʦw/T&)9euw4I`@  A$!nkL=X3?{ H}09sCt!b`m ihO.lm$.8ABg82iC$*Czf <&R.'7g7mN*371ߛKqiLd3]F|)C=9xH>5XV㇀|~3o53ڵHPҎ2nan="޿ű-{Vf@g7ޗP41,ی#FP1TMS`;e d:L~GYPY/-DDY9cZ \Gt["5)jSϽWQrtkx iok;P"|G~\ۉw(haRcu[19pql2ncWjK2eU]jkRt[XYʊqċ:g'0v0y8lRQhb{\u?dۛi k1m NsSWEoA- eӁY8CeTcAv&eݭ@ BCE`ry\zLWֻ\D&K|9}[FC#N=CU#0#0V+AzݙS Yq`6Ʌx#3;Q^̾l"6m:rN.h fἿκ:ַ G@ @!삤<_ӥ+Ͼ:rfFt0%Hi#}OʢQiT )]ɞrŏH 4:m_1*HItIY?˿8 yAH/)UF}t4oԴfm6>fsY-|LAϹŎtQ zF,:K@2vҷBq,,b} Ny3VȨd)2vw/O#{':RN.*2} kR\H1fS6.P"9cY("Οd>2aC\a,6u~|qgy;X&N&qK6ɈNo Ou8ck?VI#dnSB_U!6v=mÓJzwcc5y6LK.tpMhIgRs "RNZdN CzaIgndzJh!1b'cn_\UO9ǕՋò2m_|\IeNe qʐ٧e W3+uғWX9"Tn\xLGF|Ɣ)pr#B^,)~ >lh] Q2oVo?Nhi(*IT~Sc=j xG/G}:Nлl>Q˧ѱQ*e.L[>'7%"OOV`M!g<4A-+I0bVg4/p ufǭẎ N'3a -Ig_)fr'6ƺZmh4veNu}|V+;#fgɽ,҅H9\E4z,طZQ:Clm#jWq',ߥ7#Ddg9 m,G_N)3S,UM<XF hfXmM^*ۓy4oƽ> {*E"2?bS'tOv  HNKE03ŦrsF'yi#ҦA 5";z%Ƹ 17G/N/(+6Ci7OBki"$ 4ם f7+Gʛ[.9avP9}bPss30z< jq Fŵ9΃UЫ>%_ Ҁ=XӦHfxŽ%&qjH^qorXbafIņu'V;Hg_h=y&)E٦>F5)+i^G"![ƶ)h㴋q&rq\ ʛba]0e_mbB43ŢXڌވ߱x<6,w\<D({tOU:x4'rK}Y/ =[f'z >C\zbLOU}SwuV̍5k7R3gDPپRD(gw)rADλ,>u鲈-q ט4.[?ݝMG0vMgҝoKzt=d}MdsevoC% 2_ N?v/_ *Ke MDqy[s̋/qvY'dkz2`s( ;oᛪQxmRD3d/8}GuM2(QҐwx_Aܞ2U(̗CKoQjpGVxefeD}ֱ7׶a^{\Ya ^Kq>dń c)G6,2]7|M<oe"Uz9SmT&FnJ%ys& h{v/B:V&_8 IDAT`u cl)yg^\|GW0ܒKc:0%J5pp,^t}^N+Dxp 7&iv8+^j?%q"GYwS89^g6t_&8"._+l^~ʏWRy~ 2eBV-*Ǜ4+;uzI'!_iLD9zm¶lb>%R,W´p,+Bm0.J]JB~o?R ȫ;9t-zS.64~A g$˃ EoP|bB`sokEwaMit|Q9;gyFgr/kh(ot&<-=i)P_g'Tw_F"2D< -kC9|)tDI9BqI,V%*=qsSvhlKb(Ӷa,/9/~JKm#LurQ_蠟'JN$yhk6AJᦿ1u+R+BӸ9՗ )˽Y>a Gq-ѨH4a #z4gluqSӅ*7S\qbvVMV#*pu|#;`# Uz]Ӧ3C1R2:X9*w{ &éUhS,-cTNqJ5 ooe`'z2ty1ԥ88\o{y>l" Y}{ImhãT9UȆƳGhtsdVX /DoP'rm $ V]bvxPD :Ȁzn9zJݧ$]Ͱ3\i‹cwb2m0Q;gd~(ƛ%%?X)R R{w+Oǫ{iM*OŸr[YIJUgJL"sck7x:e30[c$VKz2nATAӔ ?s+7_%PVNRt`C*)YOwOoTuS95^C@i?wvMsD(Tnw']iȧ8(7R'8̗gos|4ZiaȔ`-߃*I21oV ~a)p`i'sN<ϡ}QU|Od]峗 Jcafmq&3c֜<{|ckQ -7#Lv~IAoQl2EJ`AT Mi^+~Q֘\5!!kUVSqiWDۜ^/!o_Z=a9X9i<^} ̛ud2m> :&2Rl)ʠladmv3q]Viw33VU8߇#^aМ'9 CUiȥ˭DU ԝɞ+ٟ#@Bem#WEHǩ?VJl(g~,POV+*5D~^O;x\J1 8’0[%o)CLHu(1SSgW[|Iw6yyWW.GCJ6h4t#yR):?wJȨMBEC uUu4bnet P S6x,{]&Aɏ2.FU4PO,P|:;nܩfcL~ T?S3ȃ סAD@?nLO=$k"# $\IZrz}3^Q'}J\ߟSq۶?1Uq(􌿳m?f>3ʵvb ̃.@hIYjiEF_b6J7}O z5Y 3W?WrBluHDTжjm7Ow"uѡY^#e匹c:ٔCCYP{h熰ߪ5ESmZHU {ϸaxIT#Ysg§TN-Ubq0V|M)'yua=ZĞږLMWKY窊4_,On)_*o}j^ '08>08pBb˪ikd0/v_6f45CyFvo&6kjRm>P^,Rt$_i㌨F: Pݧ(C*53Wxr|WV)}<#6tgew)d!w!j{ IIV4(so?TÊkƕ:P:.1b涵i{" 7MnBάFϱՏһdw9ۚJŘjC;#[5ei`Fk.@gdj,,srɎ}<V\F|ž_Ob\;^T·8Z1Fw %tHtJH7b?JހQ#6Xsww6q׌=={{|Uȧ%7sp^VwV<|1{`;jQ&IaPZG. yn4(u-F]?p!w>FV9y3GW~䛟{Xy&o&,=wR˫|Hp&|_|$n^ Ώe#kbIg],լL_8w R$[8]"+G~QW7 q#L[:NNq;! dbؠ%A0C1Ig}bL i׏/69DŽS>EΔ俟џLa?w0+weq #G9'*5902X4Ďy͛1h6]|/cMx 0_Kg;tdv gۃcfT;?Y(/V­خNpNƌngY|Jб {coJh/vIO~ĥK'pQRy߭B\kۜ2XYu'V97MɌg`pۧ{5hH7艬kR} ⫩8s/u7Q'K>-ߍocG3r\ņFQpzå!.J(7y,FFÚ6 dk OǭƆ۞ VzPmf&EHd_.P JL~V>+V}-Q͘|4c4%=;^eL8 Le"mΔ+2uފKC2*0wR˫|PFj/i`w3/jѣ9t*]"MkbWSf蟹k#\#ȃΑ(wϏoƪ#iS8n(F> ;.ϯG]$% sgM>wna4Fru*/Z}M#lisU 9͔z`;ʥW! ")?g:cعْ8Sd}ZHpd Fd鮡4}ߒ{ה`` 8ĺLLvV/ׁ~> C݆WiccK7jjb bm5JieeKܯx ep"^4$'l=lKrU g=Y| Q0DD=M/pπ.wE>;(Q[h00xT^7S"i~"INE"zNF̬7WvBDD cE6Z3!aXͭ&͇odH)!fSA{^E$)+oJvH D;YdĭmcNJED$%I@$$80' NDDD$-y/Y(I4}?bˣĬ Õż_̍#~{6̓[ӢnnTi97D])&:UB^9n!SQ ]HOU4#mD ܋dBoS4 /v Gn'䪫"o7Ixucz½?pD{rw-w:^ ׼IIr^ܺ~$РOO5S8CzNCb`>4u.bo81,얏S,M3ΖȈeiT.;O.fވe]sOug\zLs8`z|vzBwy'ռ1`H/WOΚYisy&$sb[Q9cUC5kGYHX|X7.٭mZcU!./ΐ[ ̗w[MbD9stE7Oa1mS|1=1STV;.G۳hY:8mM(6Dϧ5YkmsrJHtGƪv3&~ozmKM$eN*ոt<ׯ$$%[v(_:dI}M H nؿnipǪr1 fQt65ρy(j_ b֛?/Ef*9#b1w5ێ]L+>ⱘsZZty\*-SI~Lcӓ) +2dʅ{Lk93&5Jtի76Uf',͎VKS!" x}W\U rg$glKAma0ky͞EYh u2/eɊkڟ۬7'%Xm޳ahT[UvyN%1׊I4Z EllЏ~>qȺ,)U緞o/KR75/NdΒJ5jv2DDҜ4e&_~X#n#uCy77KAۑ8}LP|̧owsx _{KLAW9|Mk8C"X\ke]*|:(K8B8=qdZYM. JGw>,,#xp;~Ԟe'պy~B޽K])PE&L.xL~j"*jN/odaelM8,*&N/df7OhUe֛^2}ϘW1cڿ wp&;ye$x՝!\5^MQ wTo svx$$s"ƵvY7cܭ5J^M>!`QY bh3'FHc>p/SFo/`Ϭcl({D+_O K~'<_|F"F|\sJ?U~re=|_l?qgpcL;nOI{5NǺh{zc `߉^UЛlۇ2kQN?ϩcۘqi|7}Kg1Al_xƃcRqu! f$jχK71\+d|c Q 7wYa-ġ hT~:qGOCrL]2=>ƗRehjՌ:/ ;{Io֍S;pldnjg]ve$ n֝yt`"Fpߥgrxd&|lf0̺&:vNWE.^Ե0.UĖg9kUNς?=Rm0 nWpT4~8 ڤy! AQ_7l\(W?b#f3ػR=uvO.Zgxi1}E5q,hLtN`OOE?يEK|c/c:L~%CMrsS|rbNKoq,ц[xcד4cSpЅ>#ۭCqlZ?5m ʎ/Lˊlk87CZj}$)ٕ;hi=7ӽYewns`I.՝lY|#2q2&' ޚ/ٝuMtA׸2f5+n]k9t99? X79ۇmJ8aouo˱26۸T?gka0SL zF3?]Ϝ|,En{;yl 狈$m!fE(ۮyOocIXW&-@؍mmYw77*ögsn~?fzTa࢟9p<* IDAT)Ѹjgeu7Fr7@%I jCZΜ|*J<~EDD/Z4xq4<\Q59[gCza D_Тau>üs󽯩͒E7:n(=L/XrLո5m"u<ҙb,Zk"-a}i4X;tR?2H3c>LklF7l:==d<3^:: yig3+R!L;͑tY8ޣk4r~>0k_Z}וbַa[s,׷> M'XS |EJc^/sdB̚?XI֖YG))%Sw @?xw]N'kY:)ꭷ61oe yuufDEf,ʇuW?]o9c3Χ W=_DD$%?n9GަqaG-#[f${rLڰbmhA蘽3]!l{۱sxҀE3fesFZЕü/]!.Yߠ{$B/Crܔ -pyrn;96DތqM{9_38I]vۗtFf{ԆܦGxu.q(\mlWcъn@5|{`=af//S4uY3CC]V,!= k^`St[IJ\lx-GrSoF{{(q㘮nMլ|jJ<~98~ǏXdBzի^+c&ptrJj$KV˶S4ybݨ_ڇ7OfUN ܻwcG+7E݊vrDDCf-gwNe3~x,܃d45/Opj9R.g:Cڳko|N%Qgdp${Ҋ1]ӇkYec3Om(O^Mq"HgJ;ҫ ug7;Ma],i 7!TXF9ű+՗K֜7W=6 ͋#f5x'w賍d`#œL_y)޴n RXfB)׊e\+ܙ";;:Ӻ`rMJ.px3 bD\k3`IfqםXPOItFV2&L_qD5)wK^e)9l}9i&3NXdXa/Of@rÿ?/d ^I"aqImnt>}/ ;{qu-Cƌ/_AD$ ɕ3^?s7k۷r|uqi.$~ErGf}>w'&ڙ2M2aJNf4_l9Y$ `Ky@*FҾ$]{Udg(3e' zzdt>`ٕ*veDNMgq,Gzy \iu4˻Ԉ(Onyoz2X~MC(B~.]ưoopU21lej9ѧˌq+2JI`pHsNE&ovoϤgzy¥ؔDjA,4V3s%\nWL cGuIyym^,,ֺ"ƭZ=kB ŚEkjZ@މkRUk#'ұhqL-V>[,C|ԑ?;8SY?>izV}t؛:~x`{27;I%  (SL$) ݤ߸ ,<Ν1ܹM1|x]Jn=8q1B{ϗMcc# 9ˌ&12I׍ ͥA$9=ශus6lAyJ~&lJ~ÞQ I|m %NP/kK7lV'xu v؆s}ҬQIZ\D`0ؿou6b-3mllFMV-YL!WW>-%gv72b}x ,֧}(uc 'Ɨ}r(EOR0DD|6`&?ඟ.`_} h00x?PVIԨXD)1?z|ɈUvN?驝p,O9c.=" kA*_ٸǞޫ+II;b'#u:m:O61'|""$ ^EDD$5cf9B::vK$|N鷋v%s?;dL#eV, P6Q)3-W~Fh߰YbM?נdm_nZ狺DJcREHʝ03Ә5 =?]:KwrfXet]; R&…cD:9y 4pz/Yˎ?cPM;mJh%&-18^ 'o/G e d+Zz3[-r`hAEDDDD^WW0g/a/|ƽݳXz e3z5jӱmqQӃ,@Υ~y-߸ՈsKEzKn6vѮTyLI .ӥS7Τ&*A'skK&:}g.{r4.C}Ԓ{c%.ʨRv,Z`p1lWAk~ߚ C|ӡr?~=w}dfX -Q Nk+f,bHC.b^\7U.?wa(]NV0 SX¾7I!`/CP5T{~Q][2;j3MBOV`ciY.78hĠ~x 2GLԐgSMSWj&6-vيQL+7\#" ,gߏP 2lk&.\p\by#<>%?#1?VkBpup?E%u{mvw-k|ܘAA\2&g'X1-uʸٲ_zg"z)?6b3ԩ {L'2+c 淋v#=_s`ˁ}MGtUݾ#XaC3iq.BG%&-/c >7n`XXE|[`2١k<;s@lޔVk3tJK2׷nʓme) s}>,U:Q?3pj9ۮx =D9p Y1bGn6J΀>|<3/e<>xRM/b=fz+}Q[BtyR;uf~_ĉ̢c㞗8OtaĞHAZ@=~ c\d#[LZ 9˺0g! NO~zu}2V t^c^=./s`,ˌ{y H"usSEoH=sFCҊ+ V coS0jkdk2s5/ gJ<?z~,X7+c/ 2Th6>| gH:g […4f7[H%٩SZX=7y5^T .]8ɮUhf#B$iW ێռl >;@6h[k.&`&ك'@L_{>jƪʊcSԇ ĞN1kc}l.^:ѩzFkK۞v;t9gZ8n j"C|:wc Aгv~!U2E}U-C^ʻj{ǥy#EB1W؍l/֍ʹ.LYa/ѓ9o0Oך ?El}:L&#OqlMڛ@NLY[賗en \ofM[]aj>D]ӈxE!;+l,GoJa %huhC6o|iW4!C^f>s.M2CV Enzg;X*Ey6A\ӧ;G)Л[q!Ggr `78stGʣ87y ;bZ&5o6Kuo&Bxqf 7w -( """""T9(˛f @A([;2l.m|yXܱF؝ 8MVc!ApmROfl?>ޓQw@CrX9ptGnξMKyM:Ĩ%)l1LB⽮;lk" Ԁ1'ΗHdH~|Oy?&h$-_ǝ)jX:NNq;! db$1=ԢU_oB ëk1V8 Iifwϝ.ꃂƴ:gCv}z8҃BxwNOFˈvO!gDZŴ&`ĤLV.a:vۼSDh/-axudЕ)]&Y;-WNhר$Y"ӊ3?iD0蓷]+Š )$kil9sSB&[Okgy7C >YOFTxO,>N9iBmD2sN~7Zⷋv%s$[GJ W`Y2=k2ǀE<SL y{2VӾnty`՚o;/N,ߔnCR9c쩈QBΏ4~-åxuw[y~-N]z(!f%!#5EZŘI,?Nz#S7, >乛ZbC,ӏm].+ĬkL4;1d +?@,3QYpu\9IXx=c{0chx]Jn=8q1q}Ҭؘ]H$721]w۵ZuZ\d-xaήN|c.``=m8{q66Tz&,+Q#VVV8̥.3"""""""IOSSyC^Q2#""""""l1hNA-DDDDDDDD$Q@DDDDDDDDDDDDDD( ̙>WD$mK҄)Y6,"""""ޞ^S;""ڕK;$ϰj!"""""F*5/.yvRDD_x2ɲ}2#""""Fe˞rUqy_S;I""I"K;8PJu2eɒ,P@DDDDD$ ˜%jLd92#"""""""""""""""( """"""""""""""""( """"""""""""""""( """"""""""""""""( """"""""ɤ? H; H; H+&tHIwtGIwt) """""""BDDDDDDDDDDDDDDDD$Q@DDDDDDDDDDDDDDDD$Q@DDDDDDDDDDDDDDDD$Q@DDDDDDDDDDDDDDDD$Q@DDDDDDDDDDDDDDDD$Q@DDDDDDDDKSj@DDDDDDD$; H; H; H; H;X,""""""ˮu-"\RXZpr"""""^]r K˸/u-"9eKL88d^<)tӃ[ׯ1c[D2։݀OsSaaaNHz3C;2ĹE$OiDDlmKMH,t-"t4ˌ; H; Hcf4)-s69MT$@μy鍥U60&IDAT0M&1ED,--qhϡi]|N$$\yȜ5;v!z0A) '=/yv6R9s_zի^WUzի^WU+pm'!̟bdΚ 013ٓ'Wnܽ[?Adsvgʍ}FL/ `ii2ؐ={6#""""""""fggcFlmq` fzִ$&ٓ!8IMXX,hLҍL&ŒF,MqtyܻP#1.#""""""":KѨADDDDDDD$]v/ePejEDDDDDDD$Y>!\t KoL&$ 3x^Dܲ̆EܵʜiIR YfV:8,]^,-Le\YؤvZEDDDDDDDDD$_Xrєem"emwf`dIENDB`kvpm-0.9.10/docbook/mount_main.png0000644000175000017500000007411112770324126017306 0ustar benscottbenscottPNG  IHDRc1bKGD pHYs  ~wIDATxw|EǟݫN =&]zUPw 6TP"t5]/3re$wBg?<3{v u!@Nb!+]  X$;ċ>?;B ]$Zf)M9xbjSpgqouݥ^_k&qq-B|r ] bY4ⅵ[.sA5!墪ĈB9DΩj:Ҫ1j+!Dl捫pdVnN8\)Q=^%r⬪WGEct][I %n׍I7bM\$~T;V^\7IakVJZyWJVrօ-!䪫WW |T (Vp1!&z(ί;F*"rVZ(Fȍ퉟U5QR=ĵzE[k2& AF=x# Ha">,xٱQ[CZ%uR[k"`8 ᭖'%EQp*o51{p*"VA_{ v*ʓR'1s7yU֯ ѭzPX?l-SǶF< W[|cVw)6+ubRך먥\:[J|]_Z'o=Hg][{)W$ᄺhw ^jKxz# Wa!Ў謽FpT[IpRrC.vN̟Fpm~\eѹ뾷Vy]Z{ȵ]ޮΞz*Eu6r ^u]k!齂:I] V ګ-xۂ/}WFpz!b.J͵WOS^~lވ-q>" 87#<67QWAjn#^=vWP+1Tat6@3zȫAn-މ Fp`#6/(sF4>rEĥ(ݍsw~T粆| ]j ^֦7+"O"ۻ'wLyEC|^{V8(ިmGګQbJL*uW%r9❂T:Dmz^櫶B-nK V87ک-xѽD;6As>.By iů 73{7 >+P+\OyVjcA,j.D*cYeP^ך G]A].ʼꮝ>m%n_1~ ҇Knt iZi+x(WN k[X,a4+?P'D*Z? ^xy5\Ȯ@NN ?t#N4dJ1:E͈.'~ʣ냶B-ׯcjZWe=l kRux CaAD"nkb!֌"g =nl"z$iԡ}!Y^=ükoQoBpck5uV R,u15L!eeaa,< |ZrN.//[T}|;\,YNNBqbG"Zb۵u], (c\񲴈Yp#}RTg&gm*xY`0_@"  F1˲[3kl׍"& qqss"#Yz]pZ/ϧyC'zju\6u'5$܉f=+fC^^xBuYBXKPZVTa]wr]4fw-sF\RZβS.d<+/V|Atȗm.͵z=lʯLK\}mZJ閼+ Zn LH[Xؠ7   BTPZFB12^,0bPyA OM~x5<ǁW{ukhW Dmu>bh䵦׮C]P+vLZm]F !X>l Ę9ģvqh۵5þ&9}hXlcXtbm"z:.*'?:͝8ӅDki230Cr%m ɓZH-F^v*].-i0|wᅭDoq .jƣfiLaN=F|VcA#kFawk֜(bf[8B }ꬭH䵚go嘮>Z5`rK&rz>у9@h}b'MCpo_h-f '6^5lSۄ4WXp 1 ҟ%;|bڍynWV07Z: r#feb_|r`>8>~4@zE xqw~A}{Iro>r䔄XW:<C" ޺k# _r唯4>e?ZzV/΢WZ+[h/+$ǂ AAwjEݦq{3zA&=:c߾ޫt7/;Qg,yْӮ;:hqM'Zkq}D+.v{GnbO.U/}.\NW>Vp3w֥J uƥ6YpeVF}?t b#PY|BeZlqXI+Tl b*D\-{<Ӥ#\'LP|`?v9s n?y}Y2\J/xTw~ߊ>)1A2(]MFl_r-pd*B%2#FS$ Ua6,1fD$j kϾ-B:DJȤ9F!@ ,`/^|ه/W5%tWe۩SD"+,zh j0 2G<ć{D8ZH%OiY@l[L1>>6ibE&⋷+k@N- GȀ@(\{_VbUn)T}hrC_zI#Rs.AlHϼ3)·{g*+mկ$4 :.?Ul#ozx:Fz %~U*6Yd#CZh=.|>aP>ht]r\<678:n{ɒY+͆#|=¬ǔf3efsYֆM%K(,Ft).8.LWƠbP[S`Iڳ捎wP#oڼz?\ jJe6"E>mtZr[۸K.Y()-V{?>yaEuϧTFbP m甝z5mRC~?_},mĘLr~be,m&`9-G&e"3v U'ww .c%&Sy8ӝZ6Μ *y]=x!{ vzZYd-_jBx̯dGЮא !%&r}r\xj&?BJq=3Q̟rwσ<>wG _9u}AԔwXwvnAYO?b=uaW/_]s7_8i'}BQÏB+,&$&o\63վW'XwDK ?KP(,B XR(J`:KP( 0Ƃ Xx -M Ҙpe:kX'4U(Br Qa4 >Nۍ< B:ZB4& eJjZ3,g+%rk@Xlj :-5 BiduZ@IB쬳u`ya{ eeA*UIQ]B4J ]Tt\YYyhh֬JEcl41KP3`պoGuQ(J`:K IтP\^&ڝN;=={ xp_Y9@0RFP]VbTwIʶnѝt&iDH|qr2 ?7IlkP2{'RޕTq$Vo+_! oz #ݲ nSG>a.E꟥xL[n ˅~ILoyʂ;5<1;K\Ei^&HdI#UTƼ-Dr\dZTe\TjZשS(?"Ats r2r<Є،_._ˆrϴWw|9zQ?p.l! !U$6{^.N)'z|IN?B<>En+0jޘÊh8= XX,AaX ֡/C `Ar(J%J-fF-+) BI+mH#T`Wkԥ: 44w[pT۾iA MwϳNFf Vp({7ЖxXog|bM/o^|o! ˸':gƲGTѯ׆Sj-ޛB>ڀgF#BT*j5Z& (%7}{^7}q ۔S'|Aӏh6w(;O䕱=3REdA?ru%0vaY'vNyOԻWvČe=gw@(9{ݻאG0C{!͑wY"@' 2F&OXc4CB}]N|$K9e\cji|m85erV{8EG+v# Eg#kuXۺbEDjuZBӾ~Ƭ|qO3^[_Ңʚ~jGT>޼cY"ʏ,jzkYc >[@1e[wlwFɆا|?եYF&&ܱ־h<Շ$e}q{~U*XCpw7IDŽTyl,:amAZTyL㏆*NΝ}ɝ?Ok{vy=3 Y"<@po J68_ /Pb!{H$,I$aA# +>]ﲖǦ?~a[sŝJLB -6#V.).4WfNSsucg}H`Quōes/u z9-x`#{?oYrXzvsN|/3l,=׏?]L$ipjR 6 N7(3N&1%E_wugJev9L$SȳJ%"xyzH~}w}ѭ6۟ry)-wޔ'mqP/ ٝ:ÑTwݻW޽z?:YYYr£uJkxa,n+^}{;&̺g*xj3nl6٨(ʞv(Y5} ;uyyw>wx|ex͇W[G_sRZVvnݺKe+z}Zƨ޽DQ4@ІӠ`Y&2&~㺵͛gH$5OYKJI[jpQ Vbc܎N&;TԜ:vlX+H:44>1 I1Znݺ5p,'+JJKwܑIz#;|2yu~_wFGKJjej6NJ]Z%DR%%'#ЌJdi%&&%\|'j8ui(Ic\XPxú:^8 REG&''DG+JAtzZ5,5QTr<:*J7z^9S(JBPk{֡7q* be4$BCMC1Y? XZE*:4/X8B4z\ `}+4BP~` "p Bi4T{&"bb?KuB4J: B_Yw. c, $He* jj[b08$2228$eAꊊ 4׷ """CB$b5juii)5 ŏ?NOJIH8NWZ\DAJIII&\Nn>TZ'9U"uҒl>o̩_kjц/{5USo( 4`,juf\RThx-zc!Efyy_zn󻪷H!+|sۈ^aO!'4oт-8n.g{O=2%q?L=ו˟ߧϷhԵ̅>qP~}}q%5兵go;3?nÅLu[><{W@\`tc\^QhuZZZ%jNo0_:/~ZxTiӺA{/ji& mjglꦥ&[]|\2BMS2e;ftWkl}ŠWzn3݄ ykߝyk?o穭yt/hDaYndF-ˀhȒ} ק|B~n{+0|jGGWPv~ii,唱GI, u:xnsv ק tb۾Pkcۯϰ?l怮o>_?9}](;MkVي@rZwxHz.ʶH;4n˜r~~Gr TOgŎ~ڲ/z" [nLK301|eDF2b\%å{7E > hq4pbk }-~p[iNV-,N^3.R4̓{鋏c7yJ;&Vn&OSrYE!\j27X} w\\n2""*Ղ!ƜuQB3=9!#2{)hz/SSb9kXhŇ￿oKgqluܝ:@ ZjڕM#o{-7uAnYpQϽ9_,*mc/l7n(W:ɐm%$9y/Viܕ5&J 3d}iόa75sҠЖ>qGz(6艉k;/ WI,Z*Qm膞/P&&@`KeҾ}Ov˱`@FB\HBlғ)|izPHb@2Q\5ShY ƪx2!Ghkyv}ćcC88d|,lBOo~":$T'>z炪C^p^OӸ*+68FQq7X8wzx҇p&ݝ$ m6E,UDKQA`t(ufot"88gOy;S& I&a+ ?8e"K%+>3Yʨ~E e\#?]| s-^v1ěҶ)(;n㕱lN5c&BU=^h<:ĮW̞4w v=7TQXPYn=6^>@"ӈzG׀*}c?F;o,Ɉʇ~Lܐi_13 xyBޝ'qƏxk4Reo sЉݟaXΝ6jtnv CF,vceee'Oڽc+F$WȍF[jNE~NR Wmg h֡aY&&醵k22%IM8%e[C(LS:"Tؘa%T&ujL&ر#66ɔ@PiʪL&ui(UR?tfobpp\$ѱZn5} 3`0\&+.+۽kgXPxپߴR:׭ӷoxHh_ֹq]f~1MuiC Wm>p.jeN JJJB޺5$44=y|\B*5yyYgtZMbfI*h}jֹѩihshEgaCBBS҂CB =GeYUPPDDdrbbttRd o5 k èT*\7 z(JBPЅݮo{֡7q*zB-ui(`Mf>Ys %*ݷAACMCk"\γ +j} r,qJU8"@P(F$:_a{#K B ,Tg) %PP(xZqX'#H$˲tuZ'<2K=s `0IxDdppXxF)+-,BA+mJ#"X0ДSPP*q%yyޠo"JzmyY ƘaT,1d2]x1H[ V$J8Nזl>֋vu2~ ~{ VyBNĂР<1y^ו3Wy51yfg]죛%W33ٕu2jkeNmmzcu0izVՊFըԔ9"UٰEY+-"dźe7V7ouQi*DK:F9P .U/9&I?tnWNY넆hVVyfE\Sl_OHdȶJNvԒL@ ̺@/g̱ ē#ce9Ɍ1aYA B5޵&Z m; -_匪^XrW=Rxnza O}w 0LjןKԓiE*wxF9y @>il F zh2"v$&: .Tį^4Μ_j-iO *_}ZmuDPjKxmfЧk4^A TmEF8-/8sc< Kz;^ŲWPs'cݱVT xꎄ.Xc$gBeϷX^vﯭ:yYG AOPf"1{=iXG:[+7~`0 ";;G&Wx.r<;B@sNi?zm)csw뻎h!ۼ`ڣc3@d?b-פKO+jsY g( qbV*2/[}w9y}_x~˃YE{>Jls#ܓ} Gv :}[nR g0\Ŭ+&[jN\Vt]1ir@CMsòl|uMH8.-↓tURsİ2`|Her`;vĨ*Yo:;mgX jjzC`. mݻWhXd-pT./+)۵kg||\LLD6e?Pmk+,,hYYI{>B˶a7 Mˡ R%%&BwHhHzzF\|L*1-yYYY:iI͚TԒ֡=YaBBBRRBUEE%o=m胂"""%DGE+J[CCMCO86aJ%ˣ z`VBP*r]gjjJOއr˱**((z BU> 5 =* xB E*㵔5 3> uP(J` ȸ. Bi,c--E B (܇BPn|.7ˆ  hp`gxޢi4kڴY"BP^VZPW^V h$!A7/~uKXõ7uh6]_gEyBIxȠ[Eחaӛ5<.j/GSӹt`g:V76r߬( :95]tZk4Z HNIx|xX hAap*FӟUu?;x3MfuSS _x5d2bIqExuTt 0 B,]ͱޜ@YמJ<5g_}\6>:G͏0sƣFI0)27:_zQMx?Z 4s=ZȬ1{ ˊjlq?}Fێ2͊4A|0BNOj4GiQ1rqLɩ-l;gLowsW ZA zFrW8џ\S1l{h{KމW-[ñYQo0zS MXhl$;o&1,4B#X$~Y{&B&h=ҽ[sb'p r*pR$ ]+3bێX[x{u-mm#v~;9_SF[z=JYFmܽ w_4k],d2s+`k1DZ&c²1Z."9Bb}E$OiHBą6+o؛/Z417ʝi7Ӱ1=%.}wqA#eࢍpű_fovҒuQ[}`OՅad$4_zBS_^(_11oWYԻm7yuh7H0]F~[ºk-'Ӧ3?|}'EItsOE*ӧ@‚Bڟ֩)vZ 4ʖC{c}xK~K'G:rMoO>iVп+ssF;.qfh5|^guUqFUrh2:UaaK~u4_zx݋?N44P ˱M2-=Mb3I?;BЃ;v2+s,޹36::H_v:pL"6LC΂kUt:8o1?IZa={ 1 cBİ'Kݳ'.66::Hq]E3Ǧ;`//aFPUܪ5&D-"'84$55-.>N.-sYZmӦ ͚5 R@+G:n:P'P1BBBRRUEE'm胂IJ%SmA'ui(^B~ UR.k7  ,* B.+J1+ק=SPPA1\qJ\=![ ֡x/\hN&nXZ ]BP Y B ,OB4T|z 0P(jxo@P(%c,-!,C7P(76.0帰pUp0IxޢjWTm)J6x7 M9ĠחLI҄f,&ӕ˗-u:o]2ʏ2} ůceyu:%KJ F7=6g-o/w-oz??n#>~J:#B\W[W%Z.&>^xYUGGGdr52!wSfM џw\yֽ3W15#Vz پ5d'X-)=o;~ Ǿ5`mhXh򸝜d Skt= NfyfwX)4yylO?BEBa xdXccV˲&c̲N.VFTڽ .zkɥrUaOxh( go?`BF#D Ʉ պw&> Ęi.%ڥP(?e J1[,p,5zby[Fʕ&ːic;~䝝ɀa%t RWħ Y= ( 2YLv>'vd#Z;!_EkEq-忿ȑ>5?[i=P({6~ai4ZLn4\P(*+u8Uv[eˉXiH|3oV p`ܛ *&:zϮ],IeRy 2 1̞={bT*j Ҡ^3 EGEtM7t344l2`ıL^V^gظ( |㺵 "PT*UbӦ`!iiRle赺Ą*Y q"pIAAE%%'۷O/˲AJexxxb(R P( B0bG#d2YdDh0 p,r\!yED B|_۽s  R);UoIO c]}B521T`) ?SR(J`ǺE^)J#gF/D/ڃB4F@/7HP(,B ? (@6A.ŠY B ,~o@; qqay OF$0X؛bCyEE,'*'.BB/KU&bHK @hLcl4Y44<288e%`Ѩ奂 Wȩ Z.4(?RZZz4}yAo44MLp^_^VfJ&͚櫗.* ck M__^ ĺ._-Z)B8DpS[{~YNS&x^h2[4 O0z^Re;{&$8ؕ~tӡ̆vtτ~zV\Ϊu|s7UQٖF@&1 JIM iCP|"8$$55Nm7b:`RQ^juZ5:h04KJ( _[7C>^+~v~?z3֏M `Q#$_bnVC.WcڬzK 6m'^ЖF Wȕ>5Ϻu&IJMZ:"*d0 iMyG z[b%Dw'wӡ2ė# ,æyB{2ebً<]:{1-T?n>qcGH[OQ )n.11%.h 8,|}O u=7>&]aʈt%r=݁ן9}P.oy}]YpLG1{هss>_p*EN%K]zG} ll@Yr l5Y/Xr!6w-ӟlo˿{O7^d!w[f_ _>M10_[cc{> E#M/4k{*v?;j ߗ+20DմÏ_ݓkB`Na2#dV#,?w.S'<>uB"h3xHJ7Cܙ!aHlvcO|zߴ?s cMΠc|yD?ջ'G~p߹=B{.dتSNr|RЈzkFwBcb6qԶLcb2 =dU3גwX}5&D(-㻫8.e1*Es\DeCq,=|{Gx[G֟,1JNncd $T/0۷歫9=wՂciTvH"ny߫yGwX.̗>왐߿f"*C*#]MhaΔgaoH#$&v~DL*өS'>\~ 9uA1=Bʸ6}xqpcP&L:3 =6.M1.yԴ)â\il!?.sic>xΗWAtSbb0&Pš8;"eIAɽM\1 MK㗣V-|C\/V Bm#yVz%Il[?hưA={zZPÝ ]o}cc{e|չ䴕QnzW3 a1f@ _0vƒy:\}^+v9+ܵcF$Ba08mޚa췺(Q||7aC鲆W r/{DbޒϬ}L9'{^[6|'.Y햮S鸉_(G=f;̷g.~vw>{Qb$@c$&޾-66ekfTI$[:uYdQjJu7 !򡐃#uIe2ѹWr90̞ݻ&jm9X}lFczanxb6$?6{о_ چӯU9cޣMBl29 "bL 98˲z~=z 6L' q,'*Ȅ1cMׅ/czOOՄeI\ꑀ.n@uE9qVB0||βa+Q Jhھ}[phHjZZ\LD*ME9i&MU*quϮ}oa'{Sl ܜNBS(b1ϖSB ||Y~!`Ѿ% V6 R(KJKOo^/`eXRTȁylx;?15-4,\*k>d l2U^<) %NbB>,?۾A_s!Tdqah2\Perc9)ˤ "e% GP( B.HD Ap߀a0s R);J2#ȤڍPq XXuvB U3O݂j%XNP(   1Pwo2,) 1B3O۾7@ ‚@7P(GoLuP(F &Xu3*@ 񂱯x9Kt=°  BPe\hUóY X!`,ԌFu߇@uB4bxA@ 4 !:eB4r!7,XBP(P(#CBPla/ibOPD2wyH @_m  }A5bk ӨՅۏB4&As/%S|iFӼEKZjI(J#CAM;s&,,p"UzMɂ + UA5:B4J:ml|u:XZLxTѺg !F=) 広h6қx䅜S(ʍc\UKP)~7?̻E7ǎG $_N~_SyOzʏi?|,1͗-aپpmC:~P;wS!)79or\7 *#VoWC7{M$ K~rޒ7E0@G~?%k%!Ƌ[iGT}{ۓ|fð9owQԼo>2sHÛ^`y㞝4<[hڢGHY$GCZz  ?uz)>6?/?6K}w9:Vm۷*._rճ_/yԾ?ZveuH7;Y꿡פа=+]ʺaAِG#Zd\s f}!OA-,՝N勞o{F<2eTb#皽Kiу:hkPMʪJa]FX!ÏG3uB:J…'ջxl~!EY_#V`2%1I5 1SuHrUo?Tg zACF BɨeKLZblc]8=] J$.e1*M6I!-zPuY|0zܼGU5<0{o'l;/xb.?nfcI2m=[ ƒ3Xqv9D$ "SśTX6,Թ_u5I!-z|P?hWvMW~GO.APr\A0s0"UbnO>}k4umʏ^[vc2q`҉YM{?m\I߃JrUo?i-ܟ܋?}{x ?;p]Ey0Лl6 "0,d捩ɶJ$ݺ$}) %c}B躈 !Ua#IJPv~oagZ 0QP(#{t X?KUZ ,Bg4 h` _ ( %,BSBKP+ do:B!Xb3/HXAq+#˺" :EL.S(BB/K@ch0`W23+9ǁ+֯fgs~ޑUJ&=z[sa˳/ ^yam_|44/xSOLJE(2ApJ{T-',&yaASdi~Q3?BP|עZH0Ƙ.s_|3Kg.+򜅋ؽhٌg\ܭ-Δ R|t5eKN75?t>aEO;G16欜O}`wK|q!*#BixO}oQ h0dFĔlA@ޱ=5bymmdyWTJH)+( Mj1rރxyHYq|wEc2X]gB cUuP<3[kQ\ꃫ3Zi,{r  yX@Z bM#@~JlHJϾkw #eLsY_rI#R0(n ]wwᥨiWvJ)>2u#Tx\չS-Ȥ^}kHʹxFo60 ðOYa0VA+$(!NuXC UK=ۯ*rv`,MǒͧLƲ3ih#A1R b2:}\0֟qʍ@ 6I~iCi/9wC"%`I*}"p 2K4V'YO?l}'oaw5e115CNxϭҁ2IcLj5 c1ٺu/-4_ş4FciRL08/)WuDzJ@c o`[_A^idHڸ8 Rw&# ٯĭVn1vSn`sm}c'U#P^6ˤMzc]msب^n+Lu GK 1QQZ*؁A%!7Tа"A^P1à򜬬8vi)Fl9%ɝ#U(U'Oҵ+Ҩ+zG{!r V&!cgBcxhhпSқEDJe2ZM)TVRUW:{h-[E ¢{%8àDH Xd[!1 2 jJ4ATtBl$/?o߾F`ULTT|\BG s=c"NRT*Bi\?EB f7r\䈱Mlϲu+NR(P(r [ B.taxBa, LgQ(1@aBBxe[޸%KP(n Xz>\P(D0uo (J#zu_u},uP(F |]XR( !f:!,k@P+Q_Sa:xB4n7b:B \O,BP &IyJq W.^D9o"O =9d2iʚR,1%lA҆,R5fIt̨4F "*+aawޢhQzƓ'@bcϟv.gR(GZ(//d4՚`ێ yyh̭8}Z*HTO=AU7MCXHɈBuB 8Ʉ1Eg+W$Iecl4M1Lc ȼmǕ>)i`Ul,BH[TT|q'_e0݁7\7l{]\169(Tg)\g{ݟ}ξ{q7ʮXXaŒ,$.)YPvHA-Y 0;X<)L\f#u=gw^''ʔܿ^U(d!imoQ$xCڷ}+.r䚞 LVv?!;[ƌTg)@qoisKll }vU2}dj9*1ER+Y!9{Z!yt콉JJ [@;>oѡDDj[sr{NvQ rVRwX-Tg)Ûr/]Mkk``FF2HjX\w_~TڴEB!qM'7  KgXbb OLēߠ`pgᅮ 冯w "2+F>(; @-C,e+4jc9w&h>ّ{s5XS@Ĝ{鼥)!QL-㖓EY[< ֢uS'`J윜k-c^ MJRVԩ-2]Fˍ`: + <ƘœW،h9}ULC}>ߡ&cư!2[oGvhi⼓l?{ SxH^do콪!~x񲅟U9Fkf\U,lYXޤkŪ:f 1 6o s$, z<־]gddLK.MDxm)b ])$QF'*:16Ik/ MbU+BRFB,B_ճѲ>mڨW k(_.xpxdc>zO.42G=KF03K_jAA h&@T'o%I|WWW@dQO>)#p͆=ޛrUG1=>EF>Qj7j_q <\ޣ˗O80ZnӺULLu9˲͚&ݳ'-%vm*_?`\k 胖QR  $,fJB%J,˦ &&%mW}ɈBJ?k˭;^t)%9)""z1](K87&RD,UisPCtᖕ!9`<4$k3Y>j٢ymc -)-aFJCBB~-q5sG'?L摿~e~.#Q*<{EMg4Cy\a{K3_q]y}^E麷?C>op: r[c}ˬo1~}8THp`{\ b"8~e LmBP{ 왨:Sqqe C">/v._$1ׇ}h>^~n=ί1nH0$s*CCU'b"(L$%ʞ2 -.e\#Rݲ-=wRh 0=)V_#L V3,'Ba~S>n!x@G"X0| #ʽmM.`3Xיe3.vD2`CRghhgƂ*0^j٠wp_v#"?{K$Da8k)Nx0r ]YDW 顕7Xe2cay)|q۫foO"{\zbf_9~\8aZyrTx^&DpT9xf0KwiBJGo3?>2w|cX[ ̈ {-* 'f8 'XsRf0 }0̯fc}N\VyJ@C 2wJw>> B0sǁ~;!@N Ch p/AZT r-^^heO^_>9b70ϰxS)*'"]#;*x.?я˃GֳQ x1gg z^"L!v $@QrLO ]Cf/=ŮDٸb"1%Ͱ b]kh?PptVG㎷Hs^y`0d_Y]!D0 4dfo\=[= {7gwF BwybD`e3\NC+ON"_ `6[Wx=ǨUU=:֭xp4Z.mE @h2 -:hoތ~^}u*hSLsxYhCf5^9pv۠/2_x5@4e+uO`Lɜ4n_AG 1@2t$d ap/! kr^!3K3x^].E VAXv}(5O1Y=NZ 6 @ rT;_^!TBHB+;. bWxo|o chwCO]o/| ^v[r@>~vj `n쿶{JcԺ@ACfS > F3W.ža][=s1إ~CxZ i9AmN{pU0`R9T*D1ry=|5G.7 =o/6`rSܾ[H;)4ʵ'O ro J)^ Z-G_9oi .bc3O~mpڴir/}I8۷2dϖW/Nʿٽ|Ǭ3 n*['M4ֿAh}js]}oXyCV~xS3Uh^}7. ;9.>mijz$Tՠ$; RTF=Wl~vǸ}U'NGN˕Ѻg+h8Q ~;;{ITW\ tΞ[='^1#Rd+g/Oe=#x3#_ ΀-JT@2T{:2iBXP2T)삘 8cnM ͂+;gӡ2P/ce0|O]#[}ȉ:ʟ<{z(^,CvIR2;QÜU>=K16nc#"y.~@ 8i_l%`c]Wq;/wɵ"hl>;%W_c'ccoc-c>r؎;Ə>cn0^It{m^T||]r$LO_~%7\6⾹K_s#7毢v3\qAv#J79Pf-xGfw^RYӢyL$~ҕ>wpڸKWX6GUu?q˒c3Wlxw~)3:]~NҶeGo2|]H[!nԣYroQp鋮2\g-pK`+μчWRx~K7\0`"W;V]RHs{O;%G]11rR<Wxb7PXn*l/RONs޳~ߺs绳TΉլ64Ρћwl%U|qK77y_6,=~^iw͔r_]vy7s^=w,?xtIO[YlusvS\`sKYeomcۗ&#_ahGwE="۸̊1׹c&^2'lc6/Wc? 4o6t3`|]׿yIk5Usͯ+uIMZ0߷?[2/jB#E g V-/xuvhQQ*ӑ?eN<+1?Mʑ.9- i#\|Y@k }zmbl-v6J_[jZ mt҄$UןP~5/] 9KOxh'.')Z2op$zY@? 尅geEgq uWOKuY\6!R93*ׯ-ʺi y32l׮v-V/sGjn K'D)&^vqfߜpݙ隨1_Pfm-|вi]c> N;:Pt[m'.Ň'),kȺ I$Zұ2YİeLx"( Z!~H½ ٹ㠤Y-z,yA =i3ћ5jIFg\#]ouVU u+ӎvT(+؇ߟNk-t,J;XDOuiPo4çJAܱać)ӆ,]_R`O[}?$v0d9WE5eŻWbVΟ(Yʂ&|zO@5éaWE5eeŻ}m?_dGƝEItJDakM&=4k6QKft NwșSbh@5fq}o=W-@ޅ~~V2}慹r:19 ˹wԔOvӖqGYX’aw[{e;sZ1pMKGκޙ0;%ؕSt"@i;<"t|OPtԘvh 04bhaehN%sJ}%)(_t6xW,f/t8ekn3m^mlSl*F挝};:cŊ%˲ϟPzvs֔ڐ8k_˺vcKo_}=F,^p^"؋,c! dM(&N`0{6YrNq,_}Ա{,Z|i. u߼ݡvlӇB۾mq^흙J vFʿlt[d\tя [Srk[gO0SHqδ>yW_=1}˯:c9{iӻ+\._۷s΋ʤqs.r 87ߗ-_[9Km/Ay-ݜ+W\2?;OcN>y}.G\}]u2wg+ũW|Uo]ӛ%lLp ˽kmTi3ʃtp5K:By:Sx+˻]|HDiW|ٲo9/qf~n9@i6Cᨹ:ޛr4Wznw.#s_@o>c2PfyȄ'afN*+)"^v,Y/{;/{/}a4-:Nc3iZwWݳğ54{Uw~4 Bb@0@9x87@{x=s_`[ˮw-.ꂷxx:tp;;L%d4ՙAh4x)c#|=r'>=``2 tF!ցSMC B O  ˌtLM! ocFc͇l FzTBj =u\ 3Nw -u_4L3Ÿ?) 0h9>=\?B~0yޏ| 6ܳqG ,9_{iわ7U9p ,\ Lo5zϘrW]85AfRۏxQ~ji廟r&}w\=-Fx{}t]sế`$km-G`ϳ=!9_6[Q 6ꗺ_{ Eg>Kûwc(L3,=bģ<3ť&}1aƘcDgڸl~X `-?^WKuc۸wƢ~yZ!otC_cs'ޫ}7+/ߘ@fF6Fxo=:|G-|Nx51=%,[m0ɒf\4 y~{WZs句a |Ju_JAsέO>0Kxfi܄3'z*ȗ3co[gʧ&Q}Z~}<5l<:Z9b`XX;.O*v{tZ6n,DzqgL{+%|zIXkx\'?yzۧ?W[YdirTbor:WUG-|z\r?|WAkGUjTd矯Z )Qjs׿Ѫϗ>:skm,ǚ|Ι-_[7/\cg݈컯(=sՄYT7߻Գ]S#5sYr,Sޞˆjd/?(2.jƀkϚEt|.2y2I |[sO@sQQc/4P@1O)R'Ϻ*-|O{ܹaWߖkߕ~֎w_.M+߫<3% `5wZQ [:L,%]yc#e1WwtP,P*pw{eo/민r^W[;Yן?*J.դvˆm6D)e{U#m m ƆNd^<9Q)0+Ux Je96H.Ri ;KokgptKn݇o{=|󻊱p1 M\0zO0Ye=kx?; `slgϿ8?;Ҕ &F W{iҌ Lٳⷪ>ߟ{I>qM[I)ä.Cy@[e^3gl "@cGXxq0m;֔_838*j5;ZcḦccGj/-d K{#YS%r" q<-3}ӧܑvmߙjK{3SqH=|in92n+cg<|YD2!Vbn5{0V4\8c,fib ?,3ϻl8>1Y]( yK=cӴ'(wS Mu= 4b )ry`WVm]r}(Q鋓Xw{ĦIg7kyQN%AQ2\(@9MԽ98-䄢K8b"R#G!J0ve<.uL!Eqfkv] e eoeݙ(DQ*#)( q6f3pQPP8d>Cڼ٩M~!&?),kӂG9sU&Qko9S-jkvMz-kٺPgѡrEXyWE;sFL{Ey7bձΊv1ʻj@hrg7jvzR ?|Aن>ϸ/rOMzdpk~:K6}77tXqƟkrc^+KIr23𕮷"RO`'%?d E0]ξaZ vϲ(|Sۿ|练-FК~*̆4W\ivoW˄9)a IȣMtO^Z@B$G\gOPc^y09 <3oG/J#yw(v΢kϒ#Ù睡xkCy|zB qsG߹f,i_-UT>lo|n#yI4 ʣï~ӽ,]tĵղ_ƈF}W*(U uL_ï^r^k#(f]U9 \MGEӯp_,%_qgX;2w~ 1(F޴짛z=Ⱥɕ>Et =θ/^{Q|gVڋ{K=w#ޖ]o~;֌w1z('ϹsIWnsG#LϽp+gKӾ'o- Q'qCN0\*_ XϱfŸKd[NM%$9zp6xd cj;Y.=8* 󬕙=4^ [M?;=?Y@Db>),\fuϋqcW}d|loND4BE}薷Ky:=58*xs=o8 } DO&m6IBׯnϋr }Oj͉r _Ow0AoZfX|^ Wk=b@V0)FclYLf_OPz*=Ɔ$!2Sn69 p91˲]]-M¿㉮Qkz|*ox4&A 5D %pǷApWg$%% '5(dnr@[tvv,,:΄ɵ$@ rCB)?>!P1@ D !$ rL !c@ BH@@ BYp1ͨ!p !$ rL !c@ YU9&q>T3^s?t̛vݿϲ` Tx‰^kƏ 5~zt1帷%|Z46o~m(? ƃu)ӣFtn0E-Tb맑t_>Uذ+: pp|1mp:7o5dI3^%#Tvŧ#.L۳nN4㿏.8G3VojG7Y@=f=8-$tGx|=Ve7<~rǼ&|?L҄ |ĴO8Y}?̧Prl뎏_^& h羻 SRys+~ztɞ͐#0Fp-8+bE߼6C23cIuwJZ/ִι ~}y᚛<圿|{ojͽ \I?ld9I֢_cjKhP'~;W7e}a4ВRsel+_15R8޺>)|ֻӥ=z:lT}U!=>UxT6lw6/l+%T7UTsŹ_0{-ڋ__7m=`c句|ϫ+ -HB^1Vg,(\_Ax)Gosa-9Y|:󱏟yγlg4f(‡Å?~ g)Q%uۤI>ך}MPyAykK?yY=R|[._[Dc4o}G6 7\k_SR{@C}׻K}5s֊B l|B<+^ayuw.I#G-XўhPL~fٓMJҴ"iU'ov&^zEҨ߹PsS5.QRYԨ$Qdx)%oo.1 Npo;ehtL5;w9 ނfدA6yyQ}kG?7.,[ x= T ;ZsfeS/bFZ2]qKW?q5gy>ؿK>:HPd*&$C&޼kZ9z`@%rX@")1,ƀ#yv3h*Qxz/HZt55ő||z;S(9EO]?Sn# FlNSk)00gۇނH2 ~3f.Hb5ũJ4ԗIΜT ;Z4%`D2i:Xy][-{{e4V(@ÎP7HELpQ Ag]#/󸔿ZHso{O>URA4ؒ HD16;vx3od "Z/f?~=uz;k(+Xm1f )mm_ֆY;]2m~4gMXfzǠ´EApqWY\C.+ƈB(p'O1v֙ϔ \(>›@ "͸ 't[:݄(t@Mb8k-xg<= =F.HOI:$aLd՚G;l%{}o&Q Mcd]Kz/ciE7ܑ+0 )Ͽ{qH6yezmnxq 2^G}\0vΛ2[0 O]=oM㭵~z~d{咠\dItQ⇮.}K>3#M LV<$ysyʲFS*V&rۺs+؆j5usO^f,iU=x=s3F??\vݫ3`WpW-NݳqKy7'K~#9v¦ o4aSZ)ׯPi[^gw1OzW?dQEw?z4n۰^rh]skc[6/y3}h f/'v=pgYDܰѦL;9ϙ@BPxWyÒG .y{ p{>nױmE^tɟ"yk8'I>~{K ݜSw~i_>NٸWiI7o!|~kxM?ܹtͯ~anǗe|lӟyiՆ>}h\o&ypK׾ikl\mwy]]2OhZV<(zUq#ҤsEnPɗ.z?Zنyn~gO}EOx#\>ŝu)2XJ{~Oλt[~~}3c$yA"By{@ TÜ굟 -ӝwkVܬuUvM$DgNq0)M]yyRv?[sY+7-Q2Y䨋X(o6$3ě5䏒s'E6=9&\zDž"ȑݱ@ks$xW4ҷ|Fra:l*y^. B<ˇ]}ͬ"qڼ yŎf+i)!sA"˽+>tcVw譳cDlw}ks ,RD8 Gb)1,fRDO"REu,sٚ)VlNqdRh`}$lgg^zϪ]cU~a(QDZҴ=|@\0ULiFh|kٛ0W}ai.`䄒V5˴[,S so9kG)&o,Ҹsﮜ51:!&s z xZ#t0v΃ %d-Zzg>ؚk7nƾSXYweGZ:iz#FLr6E}`::uT't>< (T(bq7iWy'=pSM zJËz6V% xydR;z{I {}ˆ1Д~i߿jc_\xik?Piu{zp&8$R\˜[s.K?ټvvKכ,o~RyH ,'Ln`]G)cc7rfRڂ;T\]n4~;/<` $ԺgyydR(uӮUtDٰFwysYt㲆9/-U=sk_/ʖkL@ȥF>U7ZcT̼soAӮ=sdnzTK_g@C&+Dd4@ ,In&!c@ BH@@ B"9&1@ D !$ 7Bݕ<@$wЁ1@ D !$ rL !c@ BH@@ B"9&1@ "={ !`q@IF9&1@ D !$ rL !(-A qn?~Uc jL A<o[ s9&1@ D !$]p?6e(ع-aH@‰"e " e8Ye6 #KhM!D\D\ P(*6VPob,Z Q3LfVdh'!?2,%- !H9B`uefM&DAf ( NBdCAeٮnRj>8eY;c i(h'QQ]ZiNP[w V fP˘nGFhlinfYX,&UgZ855ae23t-MBJEjzzYI BLPкdbpO.ݺa]`Lc@q 6Ci'|`YKRFAV;;X͛vbV>_0؛Ν7ܫ|`%J x\Y|=~ƘØ ,c28ucqlbYgׯp[K͛V.)^/=;z_Q $WTS__7??HâHw>)}yVVYv*1t|޷19^[.iÒG .yg p{>n8L1Ϯoc۲/?:w}wgR tͣ7zpˮ44ڔig=eXaT`WpWP6Wޫ^MּyniW}8u-ݜ,qƵuxn7&MNjٲ9õږ49I v^%W^fRʺ+펓niɿ-[t+}IʣfҮd!8%`㑯Wwϻ̤wΩdu,LUlͿ;ύQyvv͜֏^|Ofj ϴpG3֯^Xyli3J/SknugfJK`:ޫ_a B2!xBȵ"mI k0F.QV?p˙1~_'{3_#YW:6Q>q31zǯ}-vqt(9rdC~n&O9ϕ0T,Gu@9o̓Ջ{NǮxƎ]x,ANTC{wagҲE_RVTȲ `gҲywvtp\H;ecƌK]GDYÇ23Ce S 3EEFiSvv~D֭.+ B@4R)8(ȴa0DzAJI #|=_uѱB!K%fsޙg}&R;S"DŽ@Q))uuu۷ntB4M\\\RRB>+iF*9& EQaZ\&1wQ(Z* asM?0`drT&mEФfmWB=Pc;O M8<`9&1@ D !$j 2?h!@dplNdϊң +B i."hb5?0fn|JX,E" - 50ҝ/prWnsɥsWG'MQ@!0595hз5w )q 5!G@ Cn.*&ʨ Ao4#iǞh-7(bd@s N}Af~,ٙԈ=d4wvvi53-8>bI1%+[1r~OdzFDs(e^ءqVs>W+j8gxQxڏ(fܘ~%`(!9>gn.=ZTafiela"s;}]SGE~J1#[c/i3s:>gd5&UFFXCmA~IbMB>;f}=;a3rӕ?K=c|8ej,>pH3sddⰘ9Z.+ǍΕA[O`u jA_@z0ڟMGaJXR͙]KT3v/%1!\djj3=8Ssᾣy䔛خe%: qPgH?sVB!`;a[!Wޞ),kœq 68Z>cllFTia>>SM0]:Er "t3Z6G1zҝ/pr)o(pzd?g:o^JEhJNΐu4Y4M:}Ma[DNʽЊRO[5BO Ҵ4<-7nis GiJNwf:fL7mDܳsVQdai^ɤÓ$|QةcӢb !Z5Y:,-Wwuʤ(I?Aj0!MVnoGȉ]E3}njbݱHB҈xucL ;v T#2apO>mʏJc\H1s(>kPÉ&EyhӚT-Eβ=4B 9 9:* [uVwS$ V%gwV"cL:UpAg{"DŽ9wLrM΄i ݪ)#i%nZV.VLgh̴O#H$X)] >9}3<:%lӚMouqw@-] ^*k¬TL Ɉ)-I:Up1t6"+'{8$9y &n0PxXyaƪo./`1QViPKtTYTee9kguQ5̀o#:1.t(fUJk *- g7t0=(yܨ)#uu0iQ('* <LT.WvX9tTr5fY(%tt;hI:)Lԏl Zz3Cr(wdCQp;tIʘ0dz;P\ J4YӴƠNS\4F0Ъ$ufN ڤ23.聿K Vg1@i= QiXH;bw`71u6l\#{.f)ETrJgB3oH22SR$$uG/W}`6 bU\rv&-OHWJ2&%Q|`e1}R];a_vyɱ|!K;Ôym--wnUg&;} e%Yd pҝQryyiٳ&m^[5{r w"bc7KVV=Fa78\w%x|@Q4T N2Ia jF"hôR$&:d6 WjZ&SHܛCdt5ɷrLcqE+E9kG 2=+f iNF! E',"r!/!>'B^:<)+{ewaa @N;&I8YmBb -)2QH D.PħvlXDT&b-o\{2y'}u0fINM1 :B <6>!!B7^gFllh8QcZnI9!Zy~8cEf14g,hQ ! iC e2j2bc´Zvߪi!]ptՕgdiiĴ&mP V]^Q׬pHN6<=j*DRX!('`ba8n ˚-& f9l11,(qX>fxw`s0" 煵Yiaa:rœHc0ufU1T=|Xە)BJ$B8nH1jn*"ӟu}ZMH}rfqq(C$B>w bt̻0T9VgK i0iJQɑ-IK۬g#0HIPa)9ɍYڰ̳&H!TiLS7W$dl6[e*Yxʰp[qaxѐ5<>A+{{M,!KEI`Z#-U.'MRףx0UR$&-NFCB>v$(=3Ϊ6,uGW7%U.Mݜl1gyߩ&( ^ZYzhisF  +Q* e6&YYYbM29W8`&X.1ĶƧ(@9ڄ 2N%x4489_mzN]ZPKe)6c[V6FDg5i9#R"L=l$wqXR޸$R,&rde`bD8F$Qdɠ63JH <0LMK, cehiYM|n@X(j0)3"u80t8cRXQ4%k-)v5Wu8][RF%]NEUm&ìZQP`0B7(q:6:AI8"9^[\\kJ3C{J3 (RII#x@L^ss.`wB81G<ԷHm[Z_| "elRa9LTGw92&ctaFH66t<293[Ea}(ٗhkg-ŇbJ<:ٯ *x02$iǡY焓.u87@ B"y38H9 dy!E|9 3,kم%bMnqHû|0,jerPD%(yE.@ԑ$|aŒl0Zqqu5u qi+ҝA8bt(^o4Xa` G1./x?p {V&>gY+#;%>]ժiLہǺi:,*)--^ݯA4vcB$FtF| {)v>NFȞ qb0yj>l1Q"X-5%ƌ'$ߢ3 @1D$$HLlK+X_RTd`@K򨌼5vc]]۷>uqX`VUZQia%Kpe ;K;-/pⲆn+FLպIWs$%q&k+.h12,2>h̘(x&QfbFE 8ǁߪ 6R`K[eaYC*>-';QEЅݐ$\AXyMPlwy~5aq*=\54bGOVKX]ͱ#E R΁m3iQ}a: ZXQf(±}ةyj0=R+@2D;b0Rj`a=Q8K[١.mUX_tUeÔ0frVBݥN\wEq,{hXuM-zV~b~r)oH1A$Ҙa9F'e%k%-&g'M%Z% KLTYM\YQĨy) 1-NM ĥ)ىZ MI4 }q T^؊DhJHsftƌb.JNHhJIN#LˌJ(_[ ( -eaID4dGDm݌>.!xU(YY$!( r|DITx=I(ǓO 4{WMqyCgYq1$ToqĽq|*X^̊#]댢1Ij2F)v&'VJQbi͇dOtRY)e6Z-6- W/pٱ"dkW[RܱPŹ٢LSHJ V%BY 6J*1=  6uF8]yQ6rJB"if̷3.\j2 HF}P1GF5J$139ъ]ԻPEf{167:o)hmb̭K+"s)ҝ@_rmuI}}-{}Gc W @+#غnٺ8m2E`f|&'d—шvՕVXiܙk-wu+Py)E|4/2nhbZ*>nȡn#9Jԕɕ՚Y &* klc-]ueU輥`;Kی6s@9:NP<74reӕc+-UE͎W9imҒ]vbFgR%wTAبײ|4ّGKo+ R$*pfVF,PyiM6F}(ip!~'I#X8l=qdXScEie*&%'#NIhmFӲȴD6"n];#tvކI##A#}F3.+ D?lXjߓwMZ&EE2}δUV4u[8JWW5vY8+c2Uػ0Iҡ4*/)ŏCCqA =||TT*'dX]ey4cHt-mzVzPIweYǁA$Ҙ55S EK4ItG1@+S34R%ڸMgzac'E4B,2)Qn0ǝPyYcS;< w aR+RݭF! cXm,-&dB2Y1$ ltA" 8q$Q&e>P`_y{w]YUc@Be(m9lW41*5ރ#LNЈ#!E4V]CE[^?I VE z;zǤ \}y]ٍ:]QZac) $~ dpc?;hR2*ӹ֤奖쩶H22M V&GY0>К;0q1JZ?*/IM++SRx(GԕW}J@m/+{̊)iXBVm7!0d:Ѣڪ*&a**+3Db۷pVUVNv 8i:)=ǵ?dV11}'8\lJPT*V)JjbHTRea@;E*ZDQkm9b#@o[KkIqBhvCileH|+; N긘\186st"H"tgAC~#ZZ*I4[,B2Zd5'B^:\.=pp_Qu+r΃ٗ!P$!FcevPDXL4YzAn9 ! 0,ZV&-swg7MQda@aY͖h4Zo)qquuu2D4@!^7F8h0pN?0 { :A,/˲]nRi2>d2*U.]7z).I9Tn@?#+@&+BV q>+8=/!/79(t5.gH֟q)zdQ϶h EB"CtlxEgS,"eXfvИ4!/:QHښmHe."eB0qO1uu{M΋Ig(yDRvfYscEIU|El-jr V$ed)EY=rLW]QESQ)2b4Lȭh8Y{SMʋ2YZk+6'g2{ ˚*˪Z T)J7quEw_IO rL8Y.'I;#ӽgkj6F²Q r/8s4rBt.99!(˯ltDFM)q|nk ˌ9cJ1>SK)捏QUzUUIFNf`Zp/GY QycT`j,+)OSSB [&SLL` f E冨9nd²*[l<z򠢰{lg]O[p?-tQN+R4RQMl¦pBG.Q J.Qrhs6ДX ѵ8܉b5d! aj8ZOK#I,i0!-l3HJQ)670)M)<tBjFBQubz,j伒` NqH㱋fﻀFǀgk+N( 4` =y$4-B$Zl{krVV!D)AzϼӚaյ5vZ)}䚮XkPs͌\3Dr17N.r< kR/-G{}yHjQ{"DŽ$ N)6 Gzz&mTNԾ2F\ (sN$3WdˌX*&*,K B08סՠaZFٍ>7V$&~Ex NȧdbH\Rph봰sGNTs$1NyPʸ(\_Qif1lڲndž3 M:; U <$lwEy}09УZEo0g5Tz/&Q@r˾PqbRSzV6T5:P_ED+-8Ԃ$M @SgTkahYXLƴ*>.8gLͮ:% KHIS@SVWRXm*NQ^)6!3SC,!3XV":6F38+%-L)c !aJ2<` $rh(,II,/WÀHo h˦_|`k;{'0e0U /paʪ ZDwww6f3Uid 36&#-A@,:pR!(!T(OрR%baa!ۢB"C ZMgWmۇdGEF lmk)-)S*Z<'!v[(〈S RLJLoh8tN/8:֨511щ JόSg-9j !F#H-P0LV)e2ف@*9z rL&1s@m4\rц,Br@ dtҐ9€7)⃐ !$ rL !c@ c̲n i&qHûЍaZmT^{-:61&u&711[ U&i\X_/C I-CQC8h2ڢ`4baaam--᧶a;]!JmR 7`YKS(&@LFB\wLvg# &rfoa{{н'g"C b8.'IsXwKSp\nW;5 rAg$NUU4tGj)_1wU6uYY$جp 3@&^nd0-HNOPXSSUEu2:9;-ZI#l-nr R$e)E5:+G#3Ԏ~rf $aY9 je9,j34<65lgnnMpNh@gZUYgC،(KMY})备A0^ŌsORT⎦n G)Rc|@p8'IkZ>Z;:AާǬC3qkGMqYz\NCGŬ%|Tj[l<J kTl!*4g+*7EFjL- 36GC[ڍZK X -捏QU8|Ԥanʮ ' eˎ2GN:b$+@νRRvD%5Vjߒn1!CX$iH jiw]LkN ((Ydbad{;%-N#%)l: ;x$X D!XVGK5i)Z358Ċ0Ҏ3¤4%RD$kia˂915wX$8YPR%%E"yxR$$D"Ex18{OإZ,QBZ,f 0QCRDҨzCxrI) R$m/Փf(\3Fbma0NN9VgӪ Œfn:/5 0U!>rŠ%T_xƩ1iCQbi?2-R%(>|KzЇ$o@;Rʸu~usT\ P]֌ME>I)dÑ"nQlXY:VDjKaG$Ճhh12̉m\l3B"ԯ r[LTu%2ioyC&+$3Y@MIkzf,0ahg7V|/آ1alCUqv}CU38]ey}9tw\CEC:k˫9J+m,fL5ݬe9 kv-w ZlFKkЄr[LTu+Yƚ&6,Z)[ЇCU{澏$1 GkX ʔ԰%D(m;. Y@SS* 2 RF%&+)1RĨ+0%ħggg5TUobiSUcL)T$<)+sJRY~l KIccg)E|0_-hO[(a9b{*7Q@+%k,LMV AڤH YЖM0LeU^wpNaa*,^\]^z0U"uww6fgUid! y`s¸2ZD)Y~!#-A@,:p2Y1(JRIe2VCSСjbqiQR(R|=8n3Ei];vΌZZVR(4Z\ܽ[()SCaDsBxPȓ<3.Ҩ111 SC:|OV1T"X,Bd2Zɤ ah@x%e2q#UBOpɒ&o ! %K !c@ BH@@ B"C9vPXLYtL !=a9jeR co]jtEE! 11[m U ˣcceD4ea@~1:Cxxh40h4u4aH@x(l[F1M [cP}E3'Ǘ`f@Dءlr14f@C"! \vpyؒFA GgʁHVT6%HNS>Cۚt#0]߀(]@o&wbn-`?XSt6L"Rd}:&)BB~IDAT$'m!+ۥ#DHfhml7J-ʍ9cZ1>abR52F$I:9$l55LSY}ciaEKȄQE0i#P!$ьa!sØ 9W#sNfxA!X-fH!QŦ854Ȣ ZzMhDUQRq EЊ0Tѱ6'eL@adD"9nCQ,ay>IaXIҀpƽz9PM3kk vJ!G0Ʈ=Z0 Rˁ  }>#!74}_ p,G9Qh8ۓ]#ArLK)E=mF-1 betưh.BJ&Qh O tY[t\U؝aS}DdK{ܣ_dP̲M8$! Da#5;z`Zk;-,9E*hİ{cL+b"pcUS0g3uUvs6y`FDDQ)3#,G*VVHMHp5g(kt 9(\ A(EqRK_BB,),7q4 *Z,?"D}"89%Єqr}zE!Fչmێ(m%% LQQ:1sǙǑ= 824M(Bc MB5*uttTB\\8CI<ŗ O.QDC"! ES,!J"IX,Be2JIe84&c(LV"DC0aW") \B %1ǒq(!crׯN7cUp!r݆{A.a8irb(B8Dc2KH,K.&DC19 2W9QBbI! ,,K(kB41̱d84!r8q,1a@q;& C"ǡZ1haɺ HA@ B"9&1@ D !$ rL !c@ BH@@ B"9&1@ D !$ rL !c@ BH@@ B"9&1@ D !$ rL !c@ BH@@ B䘢؄D/@ĦF HiE~CPTwm>sCKs#q1@ %6.^ۯ(@m@&N$M<%6>x@ ذw?͖P(H1u}SSs[{`$^&TʨȨXJ(7(BZ"X,LR*Y Z 1 drT1&^& BP@@ B"9&1@ "!f9n܊b}Z%LϸB@-A"q|RD*MMM,ÈD"%MH!˲ 4jD%iJzzEi˲4M%xC@!tz}\2n5Mpl|lEiYxXw2D[}g= c!T`YVN!s5j0Q"RxйB "Pedz3Vb y"B1Z8c–w9Jlpr(qj:Jql%#'NH=\Ul,Bڶo_W<١UaƽO޲ 39&B{tykk:;콧ȴ~;bČ:ۮ-Ӿ'ol南& .P"ۖm5u5RϿrT1z<=9|u` >қ8{:` |9&B8?r[cs ;?eaW̺GiG|V_>W8;?Ks ' /Pikjj<ז.Mwt}4613^c1RӧU͸[w?#' 39&BOuakɴᱷ]uL[ѷ6}ظf}KFR1}Gw3qᘖgyT! >5*6Exވ y 6?ڒ7sLs>GA@@98} Ԉ3S*uvcgn󏭏\NVj/#H[:L Y};RaXqɹ_y'{Noـ!C.km6 0sbxg+ @)95 H[]ӛ_mgd1#FzrEKf9FbQ r6-vu_ryzsqOW!}cņ҂b Λ@i/>v/gT+g~Ǽ")D`@3~R{} 3̱aIj*-m~Y&oZC ~X!BLr*3Q踎u͛6k+[l}ѽOH K(ݶl}`;)ev$6Rʘ+nwߏra;HLLCbB ,KEȁw@ъ ƘTt*Ð$iՕJdbE@ ]!9 ұRmP(=CZ<3moD-0@&z*颹łvINJ"!!Ea]Wv+--XV\n> m~VV6\.Ą@BsJ{rimuaILLMBB ؼv5ƱbMMMtԤ$zRxb\SntAK0(KfY#2h*vT"@ƞNhSՌoFTl6WL~D.ݻwӻwo6zXmM瓐h#@Ľ䔒ˋpYLS[7/q1a/?CBBν_`K;)ًEY4٭:NA]}=62钛e|TTTЯa8l6;.W+%%(,,fFl?4>Ҁ^gqE8Ќo|u #j~֒ z ȍȴ2wmY[޻m*]-Gp;sQW[l }xG @c7@Z**k] ӱjKkK#>_>N:mVӱZ(T/Eb4/aW{XWgUm+-ɎۨjYԇ>X6:q6J†hrRe[j۳?=;%wlZldOv~I9䤴sC)Δ+E\UwXMiaRSsL r-v:Nq~ pD&*6P^ƞM~tJ"絼2HIfg%.Q[w"'&{d-mqЩʗE {}W ~r{ЧwluOo# =7 a_ ?ok!@z:=4TֵIԝ>vmWSEˏ%)fv}̾`k`ߞ='I!Z/6,54޳ \R{ER2l2VFvA }KUU]v.]'imi/sye^sٽ{XWAA>}QTT][[KFF, ayP^WGb,]x{s9'R%S.۶o p*qV$mG>}Íodx&OvzcM^T#*/^}H_ dNQn~p84764Ȑ<@rr uuꃉއgl~5i_͐?Fb3ers E'EOO3%ɸ=@2XҒ̐ؓld,<;+/={?v"d&%zP.}h~Zla}p ? G_ʐN/?d}9VI8r8 bGp9W|Lx\%co,'m w\ר7w~ wU|tUwlf|_n}sK|SLs@~k1gprY'qǤ \70U${$.;o"0^K 'X.{Oa7$x#deMI8J:HRu:{fp3.\[xi1Nr]E|>ukٳllznv66nO ?\#\n\vrx<ڍ%4T^0OOO`>g~|hZ7۟^H#чiPuxk_ٙ@R>Y[>ay sWIWfsx9;˶)qK@r4-cPU"jbYd; .g,G(IqyWkKv(:^n\[tik,)'SyǸs8,+8,ŒƑĵ]ǵ] FB4y e7Jq\!ZH;|nxy^6iƿ@2^x=Ht!`1J!Q'fni5.\ vekxPႄ#Qɂ(Xm~՚zɵ .[5~NE;}I/tCu&xMϧ36]̘v^xjlz^+x9Sa1O>IyE9Sy^?y9O+LdΜ92^Sw܈bE18qح3|8ׯg!gi!<g5uN=]qpV;y3DKk+-_ɇnԵ(YxTwoN*wWdrK1\nzAb_Hk.&Tx8C85g:GM-`@[xjSRVsڽkhY:-̞?^EG?O@iׂ1c6g!;M;upQ)t^^I rjjNw -~Ě!9tXG1}փ$υ HqivOd`:w{L Ʒl Ѵm%`_~r˳D28FurݴnCMlϓxweslgl/%@8(:zKgVG2e+r5sLLsdPr|]ŧ?4sqWXz. 5Σ,">|Q\|j kχ&Hqak.E95t}LVly/l2富R gq{cS cmg^u^Off&?xnmw mهS_樣lS ={̟;Gnĭ~;Qip(jmU+&0M}O?9k:nW/d#gq @mN2K"УhrYyuN>.TUֶ$&ڴ$&%r)|Vp8Cbr6]qA(S"Fʳ%Uk"qtY-?MNp6"ݙTC.AغcT^ ƪez,7Kw,>B IrB%軎}M^f?nbM'kN?Bhi7?e|y nf M&X DŽ+zʚ\b I8z:xçd`О yJ )CN=PZm! Gv[IJJjl݇z#O}8VVL=ǽ3a+xc8h ۮ$1)!o[a86rÍ}ضѽ%2v=lWSp=ΎWfs3K.jre#p8쩬$1IɋrD]ilOuG%;TgFnv_O}23XV[vT2_֓񨐨’lθ$} =x7:nF#-;y7Iˠ ^fXɊY2ub"v-zOڤ&g4\:ح@E=0$u*Z˺ h^ݥ|g ǝW>Ǐ-P|Tz4~_p"3I9z.\,Y%}SU*Pьo]k,d;V[_=~ǙEiK򐒊GDױ(=}|Io}f?x|L4/67=T}7)&1-4CܐV+YYٔlܤ96wI567l,!++"k|8X,deeqC';8*'aYYqE "X,defq}Iv٦l\*uǶ2J7bfLںb,Kb.]xoLv+[J7uҳg1/ddfFk\)T@-k뗻4p~IJJ|\߇i۴ZdQ+krĀiӍ!-#իאJNNͅtR8Tu䉤"q)>xz.j;}H Q{I!{Ǧ %D]zfLY d]_AT7cVwcz5JmS0K$ +7=&.z1'ejağrp/eEX F',a;|7{y숵MD;EGѺ]V@\t\gJ&İ+X薇-vg\(z*Ydzg= )B$#.ؾMny,A%!5As蝶Q-7{ UϪ#vv/*gg1@Ĺ$u 8ll3\medfOejQpY-Vrsrؙ]fْŲZiYԴ4rrrX-Rs6 rJvɬ^G 8,6YCJj*99rN9X#ZdggS5k99g4Ss;w)\xYz ɩdgtHc#[p5!6x>~Aׅ#cu#)MKKgg횵$$&aX{xn|^$V\I}C={$5-Mߨ2OZN66/X"70i(ɂ= (&o2AXIHX)Laq߸ܔ~6c3=cc!"b5:{ɻL:-AS{)X44L&p|%'Ccxz|5civ-zW}ës"yBY^}83n~rg >>7~21'Mᳲt>6;1pY GHG0f/ ~O֜@!8ޜœrٸ6oruJe~=uzwhtr 6-@Uu ofrw=ߜPߏ94$7lW \|X.Dy!Jݫ-vllnڸ^66W^Mss z x%UPN^=ijl䧵?EWb"&eqFqq1 ]Uhhhg҃uͯZE/ձvZv*rعc+W=z&j]??إ G|ϿͽdjKry٣GfSz<+ؼi5ѓN]n um;wM߾(,, AP$*ȿU) LvQM DO=ȕ[ `K$ftړHH$/JۇŞL8Z (-x"XHJv`ō%1 F-z>:pءR3ǍW}bw@};r{t&]Rnupoy ΟH9}{g\_CVҊ =#F٭/rR-^( S~I_8}%Eazp:kI=zpeQй3ݺ-L2.x8N*vUPgO`{"㾠s]v%''U)џ~ pYî]쭬wS)(ȧK׮%Mӎ6ݻwWiiӥsW{C$S |7ŨE10{555jbHJL$%%.yjDG܌#z\5 *6 (Ezru^ ~; vytW5cLxG>f7? _#lh\o2&wQĢExm$%&-<6U^|T›DKK+uSS^\&ijj׋j#))iK#/6ڊn9#P"fWG/Wh[YTWY h-|뵫R͆#Bk ƶ){ RT^_zYIZ/BSTI"+5!zp {c6?fe&JH\~3Jrs !V2$>k|ݒ1j :eb&,3\7|E4Fe#v#G6RB0oՉp};AgGOU1tԮʵYIC{~Yʒ^ V؉kdrG7BɇF`gze '!V*?9]E,tgau1I+$ic ]#JN|AAۢ$fANY0CS[Z񛠊H̚u ӄ2ԍ,!}p" 䮢w9ʝ(*1 ǥNBXK25Dz%শò~ğLAI U^r%Q=D:_- zo+ᇫI (M]mW[P@5ȌDyrճ^hItI;Vy4n+KEjBejrRHCBʟSWHttQEQ6 g/A4>u }}X:&Efr?`?ވڊD!$#эLJR}+(JeHK=Bz"|YyFBD%;ҘJZ@< j2HZkպFܱ Б w! Z}J%Տ=>)!>>^,!WuFgN/.PT*%{o2:^D'`ƒ :,j/SSSĤiCWAߐ'@c>I=CJCh|(Y6?Dіtr萾HI*]P)v^b^cxoji8a+>'Pލe3 CF욂(j Ҍl(!mqKdC_D>fѓKO]ْ< $&>"Bm+Bd.lBQ*{\J}4"B}n-ݳ)4O.?ĴPGA0RAq5m8֨ϸKvN]ћ tޫ anjXӮ1 {#_)ı^^zZQ*JtT1N0VK_ʤaYqu<4t 2*}"!(x=6O]*#q0ZO'u}' ]YAr~SOH>]}Dв.)kՑLJJ '}YDOAz (ǚoc ɡCHm?0BWęDEZC |m$1~%ڄKjTz5G ˯4*FJeFXf R6|A,5+qj;NQåuZAr7H!I}жR=?,֜$T/ H_aM8Y+!6%sifH]5 cc7|G#~$B!ADF(aKJ؄/גfZqA\KW!?ROkQVfgX+2ƨ}oGzP]UOވwm{e A34NP?Hqg`$Uh(|ajzRA95Z~r}m2#rB&C h&fx'l՞7H1ѭ{G!2y%Bd-đK?whzRACOIar'3a׼znSGƵ:D[_yo dq~+SW>ŒL4I^xr^f>"uvm_! Cܮ+,Ix'u@m"8*ii7l u"hG/n 8i֮Ch{vol9G^GT.(OK;3؉(Gc32!hz1y,&$nr9VF'r}|r>d'ri͛7O>͎;hmmTxLvԉ-e,[O>9,_Rԩؑػw/SLa+ٿ?Ok&??M611vi2_5cC>}4yqi,_\֎\sc`s:0~6BBJj*Gs y/%%2ŋc`ҥ gu&#Fb \K[ٟ͛kWy%Qy~EӃE˪:u+Wt :7,~)6lrQPP>.rb_Lj׋d'u޽{e7Luuun]]K,dqcrJ ***OÃBC4l6DQYMGopzG⟀[% /_Ώ?_Q˯G8/gܸqL:O:AY4K/ka{e􍣩 r:̛7+Vkse /09X/6l(⬮_W1PYYɚ5kbq!"YYYYʽ{eD2AFoՍ=;v7&xr2/ n# ݻo˜,Xh㉯'{wĉ?#?&L>1T%_7+"EMJuu5\r 7rssinn믾b 9M?/Gq ֮Y… BcGIsf1fLyjF$q|+ƏϤ7&1'ev7Y{ջWDW3#IKO5Q̥we˖-|;N:$-[Ɩ-[ԩÆ SLsǝwPTTDyy9?<%%%L{/gq:Nʕ+ٷN:aժU8N?#m5e \z饜v\h1ƍIoSOE? }$" FXY;g.*Qs͛6kpXd^J㡤s++$ۥ<أ 8|E;PT -`ƽPai߿~qSXXHEy9/"=uwݲ::uQGeƌ|:S>su`| "A aŊ||:?sx2wNNNv > /0D/ `/<vG}?fΜ̙35P7oQ=G.T`ٹ?nӦM/'Ol6\yqם{z;_W"I 9}s##y9%5- X<П>*֟s9X7wL74=Ofb޼yy%e6&|r1u[ۇ &pEsG}Tc(5fovz $ zocUWwu' _uU|:SvW $X1/`>3?]wEZZ3^]]]T;̈́޹;)..-[ɓ5ri<.)(x"}Yv;>(ӟ„,ՍCmTD.ۼ%-[6`Ǡeͼl޼9;G+ &ݺu+Pe{sC~1LÆ ?&bM Z*|U4zh ̐CiM@`b51[H LN9^~yG ~W_>`Ϟ=2|*[8\ӯ,hS+l[n:~SGyl?99@u&"}2 Q"dgeqYgҥKgN.]w~V>33ɔ)oNZ->}zt:ٵ{75֭[g}Ɣ)Sf O=vc9F6BqL:%bqGEE?Xƫ3ºB@yv/'/26^y,օ'p<O?4%%s[Yta[o/Css3㤓N-N3=ʆ.]رcq8D8xꩧ)))r̖͛=ztybQ.D/_΃>Ć oذ|W\^R䜺ϐvo˰4rq/-[huؼe /˥{.sen0?ï/$KJp4y>#nqt?Y|Zbb"=ezP,v3,S 5+VPZRʿnL-##QF\q|[ qYgt*Vf5& ~qG(s{챬ZEqڒ4'Ĝ9sy ٌ櫰Rn馰/),, Sȑr㍣ee97ɷȖC7[8FBv;vn{pƏ?~Æ K.l_}ݧ7ֺ(dffAa4B=\&LoO8&OOnp88uɓ8?ˮ qp yh)(gW8̡dggcZfСL RR9pݸn=X:t2yՉ1"v5y0|p]ܹWZ [9FyKff&v^zq畖ϐ믿.]hjB /3t8 ^~`8P =~8Lj~!@.> IDATJׯN,nY߹\"V!1O,1UV 3Y1a5yz ]  Ymk̍L0tЙe`y&L0Iބ &:0L7a„ e/<,AfLNN6 Y5] w(-qE[]bo;ϟ(gV\ŬYXg>&LOBBA[ҥK8HUTT))k ;A ))b9쳸λرc|2K/֭eƌw!%%b+)**͖-[x8唿|-Y-O<{W^ᬳdr5.]ʮ]zۗ/儸b4}Ă(|t҅ⰼ_u!"{!n+1c?#UUs10tPצäIonW/$Aοu< K, ?ΰn]]=~-+W_gϞ vO`* mxhhh஻f2Y~}Xڊ_~Z[A}}*.%V[i5v쓬ZJVbvh?0Ic~j'_|w999477+[oc1dxcV\ǪUq8 2XoOgq7rG@ii)<+XڊВ_Y1c:ۗ[onW0n86mڤ8VZq[{xصkk׮eѢmX?0I~??HUϿiʼyYK.2,Q9IMMw8Ȱ| 80X uQYtyyy7,Ǖvۭ ޽{q뭷r7lҋ[Cǩ?6oLtlw`î]euuuQ봴  SX ,jjkЧO慨%^R]]٭[7Po|FJ/;={Bii)}o5kMf&A~x#)gfft:y7֭^uGϞ=ٶm?sGʤ[d/D222" WP|FJ/>}kt:ٽ{7555[39Ӧh?03Qiii3gr ݻ˞8xg(--v–-e̚5P:|9欳D%O< g}e˾͆ dkrYgGzPbepaw/42\.eee7Ne`^\v;s̡~q'r7~0ѱafz7p@^3#G^ի)--?5Oɓ B0r䕬[۷裏FՋ'(++cϞ=o&+'=6m⦛nֱжbU6nƍ5s1mH?9$O~afddӳgO8 1yiշ]x \rb۱t֍ÇK/SRR8Spݸn=XƓQ݋Dv;~8c<^O{6a„ ͎jmߗ׉k<Vn=NVM0a㣥]vxbS3y׋_}PϾ˴ &Ltx$'ЧO_6lXb=qGX_GccC{5a„?(,,d/d紋vY|'5-0i„ 8455Am~|>_;Xۇ~?-&L0q@E֖V~kkAPH &LAN(2>.L0aC`«o|Gx`,(~S$0aD Lx/bqPlo?̉DŽC\ď&o„ "|y0|w &Lt`&L0aBR<if&L0сa<fm37a„ cPt=Լj„ "WsMބ yEq|{4E (Q{7c,$FFM,b/XĂn j㺷X|̇7o{;;#x@ ^ cZ:/@ ̣֚@,b@щgD;1Q '"&/5"*/֠q'/U  Y2ls. !lX4D  $cWhmAG"B/iyM@^,5@/C %P2O7X4D $@ dgP@/C ^ ^w2CB`1?Xo~࿈Z-x1嚠LZ,B?,x] bopg/CrFTVW!#@ -@` A6F %o Y;EL^ DL^  @ dc &%a%@ dw@qHsֶ`ztjZ#rd$ I,"ٚ2NEϹrXboߎ}o;-ZO_'N`޼y!kFs7~UˡCHII1x\.\9w-Knݨ_E0Μ9@j1b87ۆƍLeEȑdUPoyhvjJ%W^eٲeDEG,/xshܸ1nnn̚5rdůS\9kĒ [bLΝ4hntҌ=D֭L߽{ʕG=s-vCpp۶m˖-[>|޾-[6Ӯ][._lV(Rzu)!C1be)J. ;d׮,YѣGIHH.\`%DEEAϞ=<`U>}$|}}޽;M4J͛7Yx .\ G4jԈCВ{G @P/QQ,Y?BwCJ۶tڛlSfLf?~ڵk9~qqqSlYSm ,ر<{J*1x`+/66}prru|)ReOnݺ~NM6 0޽Cqvv'''so 43fjmi\|;;{ o ^Ǖ+W?0իѿ?rέɏäI1b8khk .Dʕ~D۶ѻ<==z*7n8Kܻwѣн{7FJi|n|1ct<<^ 8$Xz ;`.]UTWXTEn޼E}..ԯ_ݻеkMݻiҤ ۬VK$'+v˗/AX\kCe3 v^Bb"ϟg…tYg_q??5mW>|:uWlʦ˻j*[$W/M ĉVYj5A)שc+Wk;}|\?5Zz :eCP))zaHƖ~DgΜapEŅujSWː`RRRXj5cƌ]Z;W.>CN^㛁%}]n]tD2eHLL"X$#G~oQD ,ѭT* [aV>99W=97WW̖W!!3NCppp --;w1glMycvƯ+VÆf;}#W~}:.uɫ_QQ:E&mD3ghּ^-tژyeK˕+ϝw4Ϟ=K&M6kjfT/ -alI{ ef8}Yf 罶hВBBB;%aKhG 25kq>>>+#GCSLig|>xNڲe3_5* 7Zԗ"YO8ߨ6зo?|B#ɇзCf qv΃R]O]O1 o>cEӗ R\9իGݺutDM~XR7n̙ϟӶm;ُԯ_ŋgj_ll,O_ԩS"IJJҜ'$&&bVG:tR7obƶ\\\YΣ6h:! 'O-Zqpss%66"ELqqq:bxY0͍xtM6-f!8KRSS&<|'Oȑ#5rM922~|iN=3d`WnqEWƍ(|}ZF,ZHDJ,.k???bccm!Zd!e(d,ʕ+믿,_FM8hR=ժU#zcmSZ*z2G oȘJNNN(Q{SrQ ÇVprrґ9pN:H*T`1R($&&g8p5j԰:uO?OԫWsΎlܸ r7o3KyM-44իpAx)gϞeoӛf@jj*/_fփ9Kٶm=ѣGlݺ;wЎ^lΝ;IHH !!;vZ4B3^Kd 1rH~g>ER[T_G:}m6vAh5kbǓMx@N^ $j駣/`AHDzdH> zt;;w.)))J*̞:[vڵkL:Seʙ3`"PjU 9̶Uۙ/:x;4ƍ[_`)N>/WJo٤$55WѡsgܼZٰgΜa0.M4NN ~lݴRJZ! ?xZ='}5k'ұc'J(Njj*/^d'{]vl@dRHhxN7oҥKqjbŊѿ?7oMٌ,'5c= ԧAec߿/[K Ό/* y +wς dK {F}"\#I @`,L"Z#ala[N^AM#ٝY@(߿Z[k䵷U6+?8_3Ƶkػw!%%Ū,;w̙P@~ʗ@``K*V( k.\@Z 7Z,r?y%c2h%I铔ef=5Ye_UO-q(_3i԰!!CY߶=qaKѹSGv邇ɜp7't7ںu% RիWYr%7n0lP=H Gx[hۆSVrʕ+x^޽{, [ʷ$&?o޼4nԈF dI&yq'_zu)°#h+07_΍7+F=UQ=ۦGEESY4LGWRRGϊVV0` >!CߋɭZwno޽`tjO{XK.#G6h!iAnN;o'lR"##IHHsE, ƍӳgZD&3OڵjiPP!nbo)UoO<᯿b:_̞=2e˰xQ(Geq̞5B^.uȑIHJJ"l2֬]K~#yMBgӓk׮qӦka[ٳipYb%דEG\!y5jc=3P' ;Mh5m262atgl[R>0%{bŊ2bpGz/{uVu͛P+2l0֮]GF Xf h۶Fmv(SÐLbEiܸutET <6SEC9{5kVk.*yY5kұLJ 砠 RRRXf5F\B?gN>o߾Zt]#WlY>sM}ִŒ̫Çɗ?Uui>AZ 7B{tBxݻw^kDSW^5ے7?@TpMT*͛7'>>˚;wN@2W\9}4shԨ\ӦM̶"m2uHTTU2eIXf15k״iSΞ=s֭[ǠA |R IDAT@mܹ 111ӧOӢ~kci[)`ŗ֭$cd.mذɓ'RؼyKV-xǐqt/RfLE=J=o^==FޤiS>d={`}3h]<qss%>>///xeMpsfȫˋ/T_'Oi)qqi>;vsfOau0"E ޖʕ+qJb4Wad|UpM5X~F*Q$'Oɓ,YҠmKWٳg\E}@$<=<(TW!g\+ZlXr39|l_Tʕ+˯i[bIԱze>L*U4={tVFu___OFu6޻Gjj*Oׯ1u>,\k֐JŊ:k ~v6l( .zfz1t@|\?|}}ڵm1FfyYVE/ǖ"n%!!{{{ (@jX0Guvvۛ^= >wQHwoߴIIjj*׮]'KWn݈BRc]6ٳ,]{ӦJ6mػ" [Sd mWķhe}IGl`ҤɄPiii\'/wh(ٳc͋g.Zj}zxѬYS-_͛7Q+Z>}zӬil?ٱMF|,o~+bّS_k̞ݻ޴ Y䍽%[1R Y y#b@dq1F l# }"= :^NIwByq#/FRU 1^ WCEH^ ^p2K@ tGئG #b@e1y[}$MM߹cmfڵoor谵~Cd8pGAtݻwsSNԩS"[>|HXXgϝj* 4Hg-XcmhРcF6*3}t8[3B///h`њ\dNO7~'o]];shm۵U߫ wMKp֬Y4hЀ3g0|m7n*3axJ/۷y$)lҤO2~xZh{0~~~.9r0Yի<`zhֶ[{[$IBTr5V^í[72x9 bo7}G )S(_9rTR|2c6[N)S,]tgggvBҥ5|Ml߾۶ӦMkļҝ^/jwe72x5jΝ;̘-^^[`!L< www]Ս L :wć~@d1̡@L< O<~]5N^Cmٺu+mT*quuR%u튷Eeqi1+wm+Fttԭ[}1$Dw>5lh$(V^Mu-.+ȏ\|07ukgSruuaaq};cի'37ns4nZ}24h[#T#랗^={h>Wd_OFN><OOڷё+W2iDl&!!Nc@TVs˘xoQ"#ѬiS"T4NIBTϕ+-%"b+z dcJkӧ7'|)|=e2x0|B#_`AyINN'%%'a F)aK1hP (@2e[6jY?W0{\|oilnݺ,[¤>S8+BCCUEvɓ2yu%%)qqqЮ};V\EӦMٺ}; 4׍n8PzuwN^Ɋ }ߪlظ={LJb] 4"}snVޘ=女?..dy ٸr]\†8| p]z*%KoM^ZmV}|9q"j*KT VM|||_J-ej*TS֒vʼnJVoT(zXt߿Ouiٲ%7n :2NNNT*M7eUyLOȑɉh*&~ #&NQZU,iĉ_(W}ĉ_ѧOKjzvZK;;;ڵkGDVڶ}Ϩxxxdi$$7v[Vd% F Ƽy?yyyq)jNݻ~z~>3JeO>|5ikܵG$$9si߮$ tIdBh׾};wl`E4nҘBZ!7nĂO߿/1jMy___ V^͢EKPT-S.]:[moBQ3f~ˉ_~alXA 5Xm~c0TŦ8M/: l(<~ؠ.Ş{p"iiix{{1$:k[dÇYl9ΟJ П|m1 WhѢ9-Z7Bf?jo΂p1^FLL  Cll2*֭_O^ ?5,_ѣ>lٲmC2ME>;͏?G$ IO>e_Ңy3ƽ_2g,ravHRׯGzZnݺŠMdNZdΝo߱VS)!1G#Я/NNN:V׃J./d߷GET&Kmau킏24q"e˖ɉ%J9"¬Pti:uH,Uhڤ^~&?̖|ܿP(N :adr_7=xuqvN8p`MiؠNN?A\2Hgϝc*Y" sQ5mWߡÇɝ;7+q]APzu޽Y(EŊ?QÆۓg=eP^f5jŋ^63>:s,E1+;:b~zrŊm瓩mIHNN& ֬]K:uB$9Wh5O]صg*U Ro>Z ԑѝF ꂛ+M7Gnl$:aii:>g t شy3Ci԰sT חmL)**J+mu JO+r=*ʪ2R53j׮廙3(U$M뱾NeWӷwYd\\]]Q&')oy;($D:tH>}bD>}ʀ~}mcD-e ԻHy8ǎQdI#H_Vzj,YB^ٻoqvvzj\jլ#S|yn޼iĤ$[^U%xzz=bM:ǏKprrӓmҽkWV^cބLJ}VM,Ś)3#5+Wń*nߞզ  нv Ւ@<#{mXf-5bێ OG>c;!1WW=]noݲ٤|ڵXr[g)у5jXd3II͛W'?1) ggg X~>LժU Of͞Cn̶A ;2} C(d(R*Vd՚5HD1ryHLJ3q /*WLپc']vaΝRNLܹ3{V5͍x*&qq1VkPz싏g)jm|}|yU2[1f+'v-J\|}.BV,"B!iQT צuk/Y~I6_q?~wZ)SCP?_@:uرc'DR5ͪX'OF`W .PP!Fw:::3 _kϟ?_bSN;N2eMch_bb"&MWTիq*>|5edj[7n}.B"';Vcv1RV-ش-Zо=9~8ʤ$II?~6A\ڵqϛϿWWW6kNlڼ#GT3.^ilN9/&cnCř3gj8 u:vbfSiW8y$ U*bbb͛ h9C+_OcH05U3[wf7!"b+)))%"+WТys^|2R9{,~mڼr$- @#k vv *U~2^2ʔ)Èٲ5,ׇG dɒ ڳ`"zNg?Ǻu-[JtRt 1iWYr.^?gF9ktgܧw(K-gwߡVٲif߹s1걔Cػw /Ǹ_"SLH:z;vS6Ds7n`֜̚3WU+'Ov̕dD E&~9r0Fkϧ!'k.ԯWO(HKKNu9ޑn߸nJŽ{ҤEKoFVg,굫L6''Rg3nr={qq|ri o.AZ%)= /7tVy/V^Oh*fvөCsAmqLJlش's#r%.nnnԯWWWW.^$71nۿ_~ABfȏٸiK#!_u ?%KOĶ9] IDAT u֡[8::Щkw֭^_ILLbp:ub˰ 2dpDlvt4>>tBjU52vgcZ"mCx(ѦU+Ǐpϫ']:wӶ+~Ch~ˬ^[o7BBhެI{\|䅋:עȗSo_ҹSGzNRڵذy =tK.R%wJ\Mܵ;]_\zy0__*UG |?G&G%4e/] _L;ׯG1B\]]WѾ(Q8?:L6u.m3رsG##52Y߯/իU{̞= R|yu Mލ! btM6Gk@RR.Uh߮fidr3v={-Ka4ߌ;ֵ 6jԨ!?fd&?]v|._+oqss#1) OOOIIus-PXm1٣;Q{ݻo)Q^rۙ3pϫ?~e_<-quuk ;8ϟ?'ܑGJjp>2+3 lSs-_I>zt0۶` eÆPdql1o/9t+V"#MV4m`oGbjVZ/E7v47l T*5J$$~ec2>ŋp" k67h؊$1@ X= zq`^JٴO(W? 5@ xʬC둴[ M5uSy,5@A6F A6d+ғ@ C=QH@ d߀͂DP^ ^ e* W:8 b@ -d̚5^7@ dww:5cTjZMZZy[A #SSsc>5#%-sL񚚖cx%O\f&p/:paM@ x'Fȱ^1N^V IKKCaoOexØf5ΝReץKl[Y ~ HHoZexLJZe6%'+IIIƧXQJZf{9l}+MdJJ"!1WgCV#%$ P˫% .^ɪ@ݑW#y|IBaBƕMh3sYnNh||}ءUVёe-\x .DpP{֩c+WݜtB@f)[{{{J(NVTZU#Y.ѷqsK4mh{-7զn=C k9VKG߹Úu}oSz4WI{i٘km&by2Ok?m6;ȴޫ׮`"ŊHH\',䳱Rxqb|$Z4kʤ_ם7osn֩m3gϱlJ>}ʕ-cԖ ?~&|6www`7jHHH\R%^YW-MwwwדqwkV%}bkׯ3bF2B^^V,x-C(u/5L07ՍĤD<=u]bR"nny5]+Ȍ=wS.\lsȏ?$55{ڵlݾSgN3d@rŌi_7}YX6C( Ȩ ,:/ΞC(^OO}(MdŹ)F0p ٳzϞůX1ʕ+qqkڸ1C{7U6d$''G-J;S~=f_8uFNcDޒ69::R-_ɿ"'~=_,:6D[GBB̘IhToP%6$ۘF7^%Ύ1jwvq%Iʌ\L헠{afND%IJNMԮFsǎ<Ɩ<%yW^Ys WJU>aI&N±'G"!!ݻRlYLDlh1d=}Ɵ^bo o%m*XgϝGRl?p}HbB"=6􉶎ig:0uiQ$ۘdFq+t5up=$js4nB'$'%S9r\9*etFnL^rOmۛATVMÇlؼ /ooڷnښܹ{of̤MViʠ-]e>{jդCvΝ[#w-o?JTɒb 7'oI_UAV[hք?D5ڋ>}^K%6 ov,\{wQu-^R~'ݻzN q 7nF T\a ΞNa_1_c@6cgoWaoY|,׶rʅw_:RlV3pBc@yx5WSBNɓ'Oͪʕ+'pʁh{fuKR * &Zّ+g.ri0JK6 .ym.Y@ym1y@ ! |lp@@ x[xP* ȂIv ZJ&-5M"%+&({-S l~#=t$G\mItgvjZ9)ZܹrWx${ws LYA㷛̍)HV4כ6G xe.>aNx`ѧžK3SOf蔃JHKKynsHH"/eh犷k_K vUK5|]IKMeg=b+gOx'Ϟ<I\S5w!&o˹,]ŁRy޴9+qV,MWRJEbR.yILLuϷgϞIbRnۿ.LP&b@N$ F ]21EJ. 4iid˽?R̚lv$7f3Ξ{ܙ'NēOQSn$)Ekd[oI@@@y_|ŗȥelXzE\\a< jt3NIe7֨qv2m{x5EWnլߴwMtL Z___0?%u:Feiڵ1JJF Ӧ[С[] TUJy6p6.2Uyѭk|||l-ɓ,]/˨ùq.\{;oiU2//Ͽm}#xr3< "+_VfzrLj51:xqq]4./,o29 Zwyej+Çe۫^f-d+ yyy={EKӧWV$99I t,))ZEp_Ǭ &/٘^#!,8JexhĻ}S&ץ Iᇄ.6Aɓ۶myݻ_ÿҳ{'NIdxD~gGaaN5.m+oPiva˘8ej*A}ˉy1y]w#+3???ˣ=NLV6m'Qq YVB,iiiT TGEaQLdd{OV?K{5kV9!9s^M6y H5X}6}!r G)nF F޽1 D3el\r|62w䞻6m Yc3fVk\f?Cs.=)"-"iNv3OO9n[nG|'`-t܅qcpdOU!Y>bU*D~*8k^|q5k:2sSy7K}dgg1Ȳ̌G\ +UG9u4]Yh{].^B޽8|yy((\t B~}"-=ܼ<~^.Xl6elׯa_~a%c0ڿY7`"|3嵹 !ѷO^*Gmۙj\!ApE /?0 |O=Enn.tޝO>.W#3eႯ3Yrss O߾Zʡ2l>.Pz&JTTdf]仭ұSGBK*\zJ G@- A%\1Nģ)le"iy)vhX`:AS>Bo0FbR2Fn^^j<= &,zR;4$xu:ʹ&^m3zAV^Fp?Mh3o^':ʘ+fTp@ hp$Nߤ6_NGkAo 5 8Y$) q$'N7 A!b@Ph 1yl,P&;wW1>^ jGO h1yUybh2a2eJVM j}+x>^p-!2ҲUk|Ѩ-&39w ~bx̬,ܽ٭#:B±GW%DL^ ݝkXlS6m,ǎ%#+&y4\ ŀ8m;xmc۱cO z4ӜJ.n3):F6o~(,ȯ2J)I߷l&'3Kߖ$-2PTEMOw9w[1+2ٌJA-Iۻ[D!J:e#otGuEl jf3شa#mcc GVc4ΉCdxsL2AH>(89\ %FASG^$\N3X5pEӉAzԔb5*EKbAo0憷cXPt:<=ub/pEVPd!xJR+4x* z}}iŒ/ DQdN+wnFK: ATj kj"DD`F*yJ.(,k !IȖ\ӆ*TrU\ AJrfLމN^l#x@ 5 7^q[%]S AMQ(5a(@ =bA6C'6@PSҩ nvM#Y@ \+(bQ8ƫ$^ E$=orV]I@ + u @  AF8y@ h8eӐȲ,[0;@ JhTj㝺7lЭqP+AUW*^z-,, )!yi H*ZDǐCJRT AÓNf6q䐗,@pM7:ESb!''wOO$'=Cڜ\mS(Pd 5(Ȳ\OGzJn!/5S̓&G%ӂ@P$N;yoZszp|3sy,Y\ggmVQմkۖ!ѱ}zCq1y]VTp99~8PPXȐAY;'[Q2A{\rWk[WuUz We% 1QOx{{_0d QƊKj.b{^z~DDD0Yzue\=߯ >[ا3&:c[;wE{2nZ-P2+,[ƞaŒŌ/ tgj٨2:Ěty]㺔ˌs2n~iYY2d`W&VjFUgJDpMƹJEQy9ʏ+5ƓYf Ǐ//tБa0PK3,* 3gϸ箩t|<3DGxyy2r9>sݫͶm۹u`2uU[ ?<kDQqɜ8 v[o`c4aBy~dD$[|{26nb1۷ QPX y}}|7vL۵޻b՚ݻz{?عN;Nll[>'WAbx@θcqL&7nd׮ݤc4P٣KsJݻvcӦVyPIM$%%U[Gx=MN8'Aݒ|z# "&/͸ot2x Ea2|=V}vp$ߎLvv^^|ζ $N~~VоڷJE|W̚D1Rp5yٷ}r+W*U6u9oOۛgc]^jl-4Z-'Gk+JiH"5gj4qjoaU:=Z\mݎȴlقCW9x-Z8d]h,WDo0صUm%RIF]#3q; ~1YYdqfRSS.,$ÿA: mWݻ!''wU1-n+Ͻ"~9HAA8}C̀-{룭>v>{Nx|YGL~PL},XϿ_dYfJeiӺ7_Utc};^}Em,'#37pn2o.m+AS:wr:)1͜;'ʕd1R jo7ݢezm%5@ДN^ 0b @ 4\2R#AF8y@ h¸`2|)F@ЄQQ@ 4]iA\;瞙ELt4i\n@"~~y<7mWYߡÿՂ<>b^W-}Z0QٻW_{W_zr_˘Q#4~y,]U0qr^Hǎ0v 1aT/^Tu8(S6xyzg?/w 7>JQXXĩӧtŮ.k'qzS\&Ab*ԭ7nfܘ1/o/׷cnjbMr+Wa@c$AAAhZn';f5ڶήa2x>>>0d n&VYk%f 7 {fϾV""ӻ7z27Ǎ!$$7ض3u mmruj<<=gvuE;$J*k? ms)wƦ+?Q^~~]}7ost:GN}TݿO_^z|ĆMص{iFT*Uٞ{شӑ|{27PIM$%'Wۖ͛drF??rUpe; +bO/}#{zKS6<;;^ӬYjT*&M+c̙:pFa{^^^6ݿy/Tc( ))SRp?o/ "<Or"+VdK/_@Dx8#n6[3gxc[gd0tw/ Yz fmcγ,$\枩SXS<"ޝЏIzQ<{Ȳ\5q¸1n,_LQq1كG~ȮNAcF:sxBfΝAo%91QQ~~>^:TJfT~r G;ZR2*:8e**p~-(cXD+Vwa$&&㧟ͳ˓`B1(i*!Kx* o//t- nA':Il2qTW[,[,F90hX̓8\N=C^s`j{ukb'YM2[\\O99xxV:(1CS#Y7^"cQdYFQ*/[~rk֬СäDv9od |\N>Â?>uQ~g;*Ə_~ mߤ)w5PAgqtؑ{{>:tȤ ),,tHK+%[8گeEAJ/'_2 2 T*5>ߏw1\~˖+]^1{lqWv ӝRE8F5?nnS\e3 ueҥu#3(taeNFH,]#WXɈ]aڴ{:vʔST6"U[*߽:5u킏%8y˖s%||}5r87yy7J1xodOzADxʢ"֬[ρ_Z&&:[". )mQ1Ɇ xyzr=;zZ],s._}aiBz硇?z>#^'X;/V[>+#^6k k^Qs,_B>j")HDHHpcIIɨ*:X  &/Q f! V,cCmĤ$}2.]HLL?$$8v zNضMڽ{/:+=w/?~DGTiǟ~FP`? ~~~qi~+]X}Y_;wdqt ֬]DŽqe,cL;`W#{p:uv*巍mSѽkYѧo_}qbZRUBh --<{TvUDFFp߽HoE>3.]ļW_Yf3EڴiÜT9\S~1k1t`\‘Gmʬ[Æѧwo 11L|'7)8`?ͪ;箩:m۸muuj<==ׅO?s 7 d=wg~+sYVGVV2>O[ܹ ƎɮB\91|(TU`UViq׼, k֮ue欧(.6oNbe>Z*gO)BY1U{Y*eN>M׸8+m۴ŋ(B^8|yy(ŋX,̬ۗ,KqٳJEyX.#=#æ2^O?]Mc┩L{p:iiiVewV#tBJNZW_ѳt:BCCw497^JyG,V) Jk|S*,yxxץ3xwT:h aʛ!;;7?w / )mLu=k֮cqgd0t~=/JK-,8F~i,{^$IӇs}ڶҦ̓?Ɨ}ʣs-~O,/??VY˃O߯oo>K^7951Fڵmyy|^w]yِ`+]X=ܥSG.^̝'$vґ|zm`K/wtтjmGĉ >|}&O-@dIDATh!7*zi҄L2={GRR"֭/>g%UWǒcd|[ƀ`-lڸl45tԑnUAXX(ƎO^ ϟӋKl(Rq|E#XO^4eȸcxy<+p}I\E2[0rp+=7ПMгGw{`7ݻ˟G߳hR gxxzyɓ& ~1,t>S0>g?"+ލƾe-9z\ pOO}/Go~,]wrdƣ,\5sf?Knn.ۗUkSe)Tc#^̊eW3gf۩r W|'~W._̹2h$'&AFT)d$4TL&kYJȨ͜='Cns-z5jZ^ǖniyJ&$,n"E:7p qBbcq]QRcYF)Mz]R fgy-"'+*IEڕT}HOooSPI*{yA;p0u?s`4b4/hLFΟ=N)VtwC|4ϼ1``x3|cǗܜ~e?1]m+dgfrϳ_V* o2/^仭ұSGBK*x_\I%>!fչoz$DFf&ٿbXU#aaa$&%m՛ӓ`B7*CɋI}MMMJz77)lByz<==tU]?EF 48T*~W-]` EkAb}Pl$6H.*rVqW ..X@~@ T |AF8Ϋx$J jC#׈ @P;{N'>뵎b!3+ woozvFy`K7fpQ2zK8|=( YYݹ66ecrQ2orvۈ|#,Q\l%hێ?AݽWAiIJN!11l3 ,O(6e2j@v܁ 7C nFcݺkSR|2z;ZX,&1Ȳš2A!yb2t)$aav>wFޒFd ,-z%/// &ČF#JC5|J%Qq7psssX,Z-)WḫJl24wH^RX,}б#IiMiXj5EEimn/.j kYv (Z弁SH* EK3XΦydK/ٺL"99L&2$ H(l?/2Eph() v},+XdI%9m%!)b:Lrb~EE6pb9zHfs{VJ͍4zKT@0瓜E؝P qftiN^R#˲ÿ>Lҝe@&';Xľ ʈ$I>d4.YA(,jhJUAiR;O9w[1+2ٌJA-Iۻ[D!R ( ר8m91eYN j5f.'$iFZh,$=#'ۇ4YG2e#2 QdIY8Jt:Bqss#=Ë 9}قZ`0N?~t:)\kpFq-Snnx{\Oqq1ZN^'F5(ȊJ8KE)ސj wFTI+F5"NVp"8(1J-!Q$ K HH HRɍWV9kEU" GQ񂲇@P{t$82C %J7T:RO AmPd *+IAJVNa@ )e+4pMe + (Ȳe vUTb/uBVd$y8qv$O A]iS(@N^ 0 AF8y@ h'/MEN^ 2 AF8y@ hyYYVe f\hZT*SZ5sr[7urf(=d x4-kפ]mU8(GĄ6[,5ZuXX\+#uߐ&4jD'Î9V3AphXGyt֍BEEU#@qa( Q-[rA|}}IMчsgZ9yBNN.^dg9Q lB@q%o\;:FI~n*C4QcmU=Ņxj1w M{}N@S#EX(&!'w阋3h-mۡh a-ĘũcOtm99ڋBjӇ*3J&s$jY3:O<p?=EqCW2wG}IhHK5ʭ\\ t;S'9$ZcJ[!CYo0艌d[\2~!O<+Vߎ7:u`ܸDG2d~y5j$F[gM\m*[9tJx Iy oI{=q$+WEv6l(qq]j>U3AݧP*JQsV2Ic8O_Z?L@&pi1lX>;;'Ng_PPPkyvgۭlr1zoj҇!!9^Y̠[q׬Y0:zv?q=wKVVv^`ҿ?kf'x7xׯoU״3G)Sйsm'$`0nZFqN! oYfSNf8{+Wbkݫ?\F`*2!\C_(I'XŨP Aa-lI&U47o7F:ڵ oo/^^λ >6C9OI`ƿ3ih:ula{-G}믿JTTTy~@?ÇFx牎nIXXe8 |g?MvSM\MJouP 8m1wMAX3H{2$Ѕ4'd N&=z@] .7+ywKm6s,[\آ8=$b,NNrK 6JR F"W*Z"--رcW 8~~L0A\~˿a}hhӦ5ÇgvvۈrMm#E<U8؇XiK;ʍÙ9JzACo"Y)Wҫ1Uϊ[nȈ*e##"6l(֮gdi+Vd޼Wo޼FS>W:Eb n32xMtݻ`AKv%Z~{~j_;gL?*?==+W7;'V/vlXoVf$88cذV\Ie>+y3~h+,{>r9'::y 8?7EYP{"dt]"(S"(6ߎ%-W i鸹{4\}UpYʏ%$$y޽xBKpp0} 9y$7n{nٰ~ Gb5W%/aw~_ #G çyg0cbz덏;WRΖ?3c#̞Lr{7[Ȳ† ؾc6__JqլϜ+KG=i_?<9α'SX{oP g䷣5a;f== ƳhrrrX0KW0ur?oo/^2~~~9s/oo @jj*͐!s9֬]G߾%2_PocK@მVK9|Z9Sf_vhCNNǏ˯2fǾfGun֬YG8sKnyzӽ{WwZ#=QDIbΓtڅ:Z6wHPΊڷc͚u$/3Ԟv+v&֮[ώ?h@RY8p@%}[&11<_׼پ@S EQiDTϷJbT 5ETp]4k{?I'9/fvXzE=9~%۷JeiiNthZ도|22a͘J6Q $)92IIXga4zMII[颢.G%I T iEaW.t:L&'L'((R_} u35R<5IFBŔGOMF[QMHvxݝ>{˖C$ w3?ʏ Y31L$$$rV^xx?;5?<ӧӶMhPk4otjU#;t`Ϟ7onSfϞtءRpw9yG?nt^YI[cqFLtӊQss| t̫U]6&ڮ#4l׆ݻ/_&))_'fǫLˋ4BCm878yȵK 22FOu}<ǢFS(>͇T˖Nc=ȊLwC5j""馁<7ivS~=ZX/^z|">\8fAy3/9T3J=6aE(/s 7ر0=ݽQ_ceq#[pO3-qLV5$s!e&NPTLoFQԄڿ6wQBm ZZbc?ϑ5_}h`%nz8#J_>tp kZv?T|E}TW#apED&Odr}eԝ>ǭl`[`Q>uu |i=jm/7Ônjo+9g5VPRQ%  0q(t#*÷I"{U :˗/}v~]+f/b&k|ḇ;u .rrृ QTXȦMZ o챓 󲥝"߶Pf.}h4Ę웹fqԝbyx}&ʹT*20wߣ믉6lrW]CH~HK~SC9B!nƜ4"6dyHe,y񲄶;QTv#er2:n<f[(HX͖xRQ&5\c]~?(޷}=x̤ y^/* 1jfx}(ZMkE`hp:@UYf ^&U^/4).,¥,+ sNb}oOI0@*mܺNNN.yynTU~JÉjx}Ɍ+n2fX4Ӊpd8OsNb5RU] L2NJ (?s=\tKU=HJ)NbNjvQMY!ґisJ?!HgaNB!~ !D3Q)B @5 BtdD TyHBBdQBt bk*{+Ɗo?AuV IҸ+izܟRCdZ&JxܘwBxJq _1)eR $mA]hz|='M:ؠ"~4X54E1![\|Q+ó1BQHo>TB*ƍojb`NN?a:{^xKp:*riX!(,@d%DFumiPGB9@ch-x*Z,<w)ͦ/ ?m482ȡ@[+(Nj Z<-Ui8~S'B/7KmBYe~imgJl9::qo <Ӛ_ 1={:K(*p f3a ӌN>rt:l'$t8lVK'Ϊg򂳊Vsc4xZZdL 0@/*j5kT54쨓F{Hk\Q邶8zm*zvp88NV+8NӉj5 (J%Xm6p8Mȶk}> d]8oW,+u/*Xqܶ8NfT$!j%%1! sWիWd2 ,fB8}ժbbw2 *H2+5m"D`7h!-@s\cՖhM1y0 A ȘQIB2U 2z|= UU z zC91A`n7]6_!-Ẕvs\!u/KH\ s wq=40ԩSTKMf7,nԫ__ΒETVmbggrJH;CK.aϾ}'$KYgssQ%Y-NZjf)'Ʉlrwnj֨A||¯"ٳJʞUң[ʒ%ʠU tFEneF5z3‰upSi80Wq>/5GoN'gΞyQcHO)nSvp6/-Z=הJ `N˖-޳g75kTW"8 hժutۻwQzvH-ר@Xͧ̔J+ېĤdƼ"v%bvL+.:ORbrLhHJV90y$m |q9A'MG NJn[dƖR#'s9q:Ѭ<;S9s2RrϜ\.y+G8%\ΨGY)͞6W1'j͊ȋ;+ gp/r` |V,&$N*u,M,&ɠ5?%0cڙvPf೘ۿgUV(GY*(P--mLj HVmUV"=4XCh rjWP~.vycX{UGМÇA&@iobpY 7;"/%/ 5jTW\oEK3p vک5kwV7 z=;K5OSVV{Y)Oep!= aSΣfHY|}WǜEl8쎈ўݻ;˅ >.K#G@D/!1#^SBO\Fh_(qg.<y+3ӂԄu|.s5/¼.iLKV5#YȰKV3RZsY;xea6U86Dvth7\ʠÃQ*FmKmhw]4+^9cyQd+yopwڵlIk1n9G3nтW "{ \wv:d}[{c¡brU7Zڷne׍Ӎܾ'.2u*ť%27zqZcY&iNixq ~G'}ˁvR&_BF_ȓ>@Xm_lK 0Y3fQv^d[X7v*{fD f+tJo<ʡXUlh*Y':mk8c,p{E~@_qnސEN(fl%]֏3"m㖞+ً&{qo]|FN6&eѨȾK~ۻ7`gَLVyoa;X`3b ~yw{sËC?t>1;Gp @Ȝ=VL#lO3L>5ސےa<_ߟɂ䇯s3H:,zC7I/x/ebI{h.fƭ9镔ͼWg`-\F'xsWH<;>m{XNs~0-~~+y[ulڝ9U,rTc䣱ӻ)SOYp! J<+('f~سg/={Z;c HJ6y7}̉7ہ$?/gg_().pPf_MaA>>z?Dapv{HჸGf Kʦ|UVbLDLP=F6}2Bn?¦~jPDRy& <I Q֝h°mIF+&:^$u`Đ˩&|й3()+q"JS"oS^zg6[scoL[E#ƪKpƂ7e◾,y1`3g\IrǍIhזN7/sL5%9|x5 Eqc'ڷ } $u4cǍc9t`/M0yT,'-9a9Ng$Ml<.Cdl;uoAj E")f]. kZJ O|>Jn^gΜfĄ!f#!1lTIV\>7b7g#''D6v<uP +7BWݚ+lW1E𕆂x,] q&ֆŌp\xˍҌ'<(3.L,>tt+aF+; ƏYMח0KH)˽=gB+J]wY1&WnI"ap=ewitm Olb,;=F`x{d@^OIJzu ”F&V,]βgg۩+-CD>пaoh H{: 6S݄)_i"flewc+mn,Qz7;Kd%=ۃO}52%<<|f|7f$x}%u?)20yfIk'>0XimavR*~-6]}TzVdfdu"Sڡ؝M.l6[mˉo3{ v%oN슧-fTMMzj 5."5Uz .ݛl&5{wjEq-Q^6g#z S((Rbgn!h֗xWʽTҋ3/}Km !O1'wd[6?G}A|9m#9El6F$1_1"^?`sٰcoIrS)2"::=ň<4su)gn&pŎ$l;_MLnq.F8H;߲-+31Q/vL!m%;Pl*3- =e2mfr<]Z R=-eiu/9^aԖL5Rַc??q2=yLB@0TKMc ؽg.&*1d6S-vS>WϩG;!E= &m\b9#)|=v9BaZY͚|w$%qhn lMjj毜I[?sԠ^BB|-m[o#!!xHLLFCbPzɉlݶ$> IuTb wV&7QU@p0R$* <Jas:}ƼD˵}yn}^Ӈf 2|6>/.WY %n2+29%wǛꑞ\uLv!#1ҨWWB2[SuwwOd՗v@d-x' AVmɍ\ҿ=mZ aeYunxowO`UqdwH$٘A7 yq;8^Y߄>~ >==cnO|7#U#ӵ< ϼ} dqħW /^)n^520#}%bo} Gr,EknZR,:itg׷1}I7Y#j+gcP|YmQHQ]ʴUIdRuRP1~fRNB$n[m#JW.n$d$F.cݚd+moN ʞ `NBR"[Kp8Tp1'JsvZqI!Ʃ\׹5C]JrRZU?>Sر}ɉlӅb6[Ǯ]pܴk׎z_E=*cuХv~A,?_7ǓR-Bb@ q;0YHLń:)+)-E UHNbq0Yp/C%**-䪁{-PVT|d2cK$1d3,)#~Ad%19widdʒpJBZHq ,f^<.'.t jgb6‡-LlYH< IDAT?ad Ն-L?6≳/. f8^Xq:<`wh42+l,qجRzk|.flV1n,PJᱬd5{Še_УvS(7RCsᔹ?Tj;?R7Ơ$TYI4*UdnOӠAӃLnGOz4kޜK60 n7'O/lpLN'NPz2S~m-rݜ<(\œ3(svs2+CRXP@ #lg?NZjd4kFz408{^fHZ3\9 f{ 2F 8#?.hs:}399x(%%ڵn]B?nlU$L5//L]`H.V %"Fq2YIHJt"/2RW7i, >؈ٰHGۉ iM,9qt-EkT"(\C~~NeCqQkU]u֣zZVUCMAAN"77b*USU6׽؏ V5wAA!̱XtH}SOKIQQvjծE:V:;syҽnN<PV:j` B j`#heD*-aFQ9ڥR#yB}\[ƩUS`3d+ک|@&*e Q׻\YNVɴ7d]Q2TbusNq$X&jKC)HK>5ÏuD2 sJzT.؀L*b麦hrUAP#G+7C: @hh-ڠLu]'RGttpV L*2р\p !P]QVUc@oVVe .c SVU6r)ճhÐmG"gV]a1d&Z`vJlcr8dH_YU&F@P}@aBU+ (_-7iuJt$f";P\ċaA%PGdSH\z,+XMLp hMI~E64g7+x쥦 u57"MD,Cݐ|˰Mm HEd^Ҭ@l9PH^-i㡩rAZ[7LH*ڶJ. RQ< _U ,3`s@zZ8r-l/*O}?֝ZY@potbxC@l#`y9BFp5Ȩ-52Qu ep9@ -+5MklH?–qLs̤""tCVcu®:`Gp j]L 50׶"m`!S?('\EYGXAe=9@Ҳ?ue 2 *@_% !+2UډwuHSySZbn檃fD:U,,-6bWamX6 夢PQG:uD-VD/9@iٟ ґzƎez*wf hfԑ2rierw+;@QhK~«Y=-ؘT&a+W阈Nl$2Xc̎Ɇ "u5}_z&t 45Fpv01e! fڑ+VT솿]# 51t!1뉕 x5XE Ae3b5 cV}rr 5yͦC Cup"a3 iJve(qmd5Zڑw.yl?X=ctV)s5,R;5` c\cZY`V}k!ԲY*" ,cͪ$Q2sR:& Wau<BZ@55vC%KKHLj966 d&jaETPprjcCp(&2V #8aINDqE2gr *#-= 1ƄІU 0&Y̴`n"qWrtO{2K"լMt}4 hksKh [:Yu`ήm̰u%VG-ӎpDhEkwF^V]4&#N"('nxmt%:!P/۟beՁ|15< ዯfu2- c~*Y݇t:Hw~dL)};{ݡ|9c$Mm\+_w3v=ĪՁ9T6cW'/g|Ã^dMγ#/ '`iif yN/1Tb NMkL)yw:9GeVa]e_R{]'@/&s|\vGY;;󶸀tm]Bg5UHj2s{F,jyxM\QdJ9&0k[xz r%F sl\:RYGz'^dy|4f oL yә~7YE^^=|='a监#+^[=Re龏/qߛ[p_<)7;sq~=Wξx*ƽW'd֥x*xzx=p.ߘ5y: GHL+ob{R'NhK-A3=ޖs"t!G1aB6+gI@@#r6ݤoX!C}0>։ ul=o,na~xo.n_}_f; ]9h)vFtܙt+P}zͅIm/nɡ웈ml,7ҳAC%]ı_hiHk҉nm1=oVs"XSi| 0z7I9WC3复+_wzb:g&` go%P&z6! ?͝msܓM>#icoX7fhҗu!p Xa/9eyqU_=^VV\OxP39$ ??7SJ6y?>~3$[_ay.[vgQE <>JjʐҘ? w-#KjGx:٫Vs-{,2aBnw~o:z96/?b>zpoL[ɮH^ ecZ/rcg@?xN/`b1ϰW1=2ul:^$9icERB>y(Ϩa/1u҇}/1[Y)̡0ǔ0f KK -<ʺi̗qF('['KP5Ixsr/>6?n=c7G9 3*xr0j|`ɺ#H!N9/326XoEPu4`E/o@' MvdgԔX|?MM ɇ>߬|fe2 3ToMEX2K^!'.HG ޤ=ys*V^*dɶCdYv-YOֻqEl4Nd+_W)ҳ"~?EV5ϾDz+X>o:=~5U}JvҎ=1K[.!q Pσܔ KoSuHƢLZ\W839ěw ",v -ιܓ|@KnV8>e=W?dR~UʰB&)OMY!mlVK;y9,[>ɏ&RsxiX@b88N㿛ҕ+X6o:OW<8P'huy[0miϮIEl||pg IDAT#V,槩9U&l'3mɝ, 81q]Ρe("O]o`y}@ nZ(bQb-Ϯd }2衒팚 ȄGjԤY>{@ު%d:-.{qkpq[\x<ڑ!άXd^!'/HTq_ =Xy5J8AEow@jWпk2d@#l9l>@|\eaΣ,Hk/3uQo1j|:;P{A {0#[w帣3mD\ssҽ4u&`h$MfnYyï;C>\C|0F {Z:-~AW(M*}^͠ɒNBs (DPeJGWID#,[wJ@l?Κmv>`ntOpcJ6pՇ:qwd:Kqps|ӹY+a+jr}2%cР,wuı˺؁f>:ʔԆ{nYnx9Ix8l.kSU3B935w^zg~^ܶWYuN67zkM'{;6b>ֵ)"R{yLtz!z4oko%;}m\:n;NK~ղqzDc;P3XCGt ާ lZdsѵs2,kX꧿]Q9Dxinz8*׭4ER FqtMb``rϝ4(<%ͿX1:e΂C8P#6[B^I5G1`B;zR8h>0\bΔԥm\ %ԤI k(\֚MUG:j" dN6ć jl|^i J>4!B`єg)@D"_dz5N9!2CCr92uE<[3jXel3p6\\0C]p w^EoW3I~]ݛF ,ԹAnHN~c?໓@n|9OТV\P04кN|Ȁ%0}4I5i'ؔi:rF}.ke\짳5/G/ [ ]BR/Ԯh(?XC ~%# mo4 + (,[o4ǻ4~2+U eK*fgS[W4JRlݟJ?$sld5uN}Mf|=b뻒q }.קV1w03+ kƗ--UHfI=.Xb\dIz2Pt#ynWgkdnۄŢ (&9dYqQhapC𙱘_/$vG7s2(i*,?qpd: HO&$*pD[Km X ̓ 6Aha"1Vn3_e`4Gi=UR@*}{5!Qd9Sr{hƒOk. D*&(UšރM_71<:6\A?eȏ̤^яl؜5ְ u諪;&ogK }e*ILX,`6VN8XR4Ps@PnW2k~!+MOFЦ߷|j@E~\9\$_~r8O] d{&$*:D7-/,- XlD~j'Ņ@o!+\nY/a=@nJV sݕ)@s_}3ɳ{q;ϓkSֲr1YaVtmݻSXY0&@B.\/=?ۦB w&*Ea&3l M7$rc㣨̖lI$ޫ "vkXkG^EE ˵bCDD@z}gc7&$dI;fS3g9uܕ(w㲗w'Wj/ !z㺘{c0,ShϯC޺=(8gpxj~UuXէ1BϽ{q$L^{YbqfFo<)gLS>hc (l .S.G{mͰRTh12a$EIUũe;,SxR񖕩H:词&-`_~.\biX>i[1tbo=mqY[gj?;/v-&(n;jj tQ~0wYI_\e)'ml<Ϯj{~˿AE=32uJ9 ~~u/{.ITcsݞ,Ly.|ժ|[)ӟ*_ե<tQe3\~)8׽m¿Q~޿y/'H\zyL4}jOP7ygw9RY w0u<{~babU iiw}Y֯zY(@'ߑդpL7=OGZwXt >ɰƛ6EG2553Ԛ0巣\K~?|ڍak+U] "RؙJ9\eC֮UHdX}sԏ~ ؿe5~YfYR$n>fa\ Jz*nKw|͌M&圪 I.毕3y̹:U݋}zeBC-zꋱ\ oYg!M"EAV.z01ܾ)SU˴eeϔ7LbrsPC u<#{]s:Kw|ӷ>M@2';|1s5{Y9n[N7Nr]_3Z%cRBԸ37(E省VΡ?C `d9*c;s4~{>Af\wi?sxݗ_r#Orqh oYk$*3GˍoLedRw]h=/Ffb)3]/?@&n009 wͼøIՕ*DO'ߙΤF+cs4ՕmL$xr &Vv d]O]1N/T< ״.L|.Z ̩d/O$CgSYK;g|%.d{$T9]h~?s0S2%d<8_ 2UGaH{DžiWW~P8aq.a@O|^jW0^5ԦOj]G]S>ùF}Ws}r&|fV:\ʽo=f27gqe357 )/x^SUSNW1[Նd_or8kT|6q1;xWI5ޘyi:Ɂ%Hv8 9 1+YY0gDcɺ9]1s!X{Ʀ=и粖Q3r@2:3pL[3:V#T4 \Efcgq;WN@kݼsο?EO> ^W疱#j04Uqkd2%;@ijxSGRh7Tnf_{_ZNUձXhZxHkpJXNH3b&(Fg0e/^&ڊڑm~ ,7h|-d衁G26'M~+x<'k{uOrce$>1IHAh!ۯoA Y%/>w%pH$ X6 :^>LFDC<6W/HE>ʏ?yt3"[سq?8`  tSB yݏs{qW99G k$+y9t~yx V:*J)k>x%Pn~/v*Qd)d 41tw!n5a{.GJ}x{]/§oL!ruw![1eL6 F:c"٧S$eVʴ`k}^ݓ_BASz3xٜsj6Q`@ h<7hOAz$i 97r fՏy?ZMpTtnZaСXʹ{A7p#ԶVv4u< "㮁!a-Zzk3<;9r,$ݠ.ZNbǬQ\S@d":FnZnK!6C4FIJ9c:s,9Kݻmױ㻸x1$۰-5 AHiam@E}"#|,QG2!T7c?mcPF*ˡTT kPuFMg{/[Q+Xɋ߅q=1U^eoqЯ(AJ42NRW.!,)w0n\JnJRjI(eg7nBvY BJȍFHuT=k$7!?MY!L뛦˖qY7|-v + T)ﺕg,g{nN KNV|, 7=eêۓE^nG+ܸ+xK#\?h]<#b}>o8]97]XݸZ2'a#Ѐ7_Zi>i-r+peXOwFbX[ B-ld*jH: >?'(s>_F+/eɮt O&v*o|SkWGُN">cq l1~ٌNޭ*Md=7Q+JdIfBG3^?g^Ƅ'/1ūBԀ;y2dDuYځHj853g5go̽ȠL x $kP|W5^!s)O6Xpxr;G/n};&҆\M7%17\~a ]sS%P^yY8@a5$FMÙp_2Hu#ό1\s8>F3uL/5X BP#~<>{@ _br`>z{s^EU X*<͖BPH%@ 8|~|>=dlyO3[haH5b[ BKȍN;?{%5:<*5&ڸ̡` ؆,RT&f0 PֳϹ fG@ a\.2 IDAT'.+I_$(cYQUp85 SY>Պ^Oc A@ףj !5NHʤ|,:S&|$@=s1L]A[fs0.+gY)..&K&E=m{ @DEG%+m[6#t1!3hJ(-.U6]'#%E Iw`Ň$]qnNI")1Dޑb@ NK%bbj}!xqHfEnES@ 8)QU[X/@ 4pvpEAe\6J h%uۺ!ڜ@p4*r@HJn6Z -j%7/I놧iHJNd67mo{Tov%3BQAAsN(&;vd׮]Hp놧͹\a~3k*}&3;vb׮A[i6\ZVJF.ج6[sN86)ڱw[7kC~Fzon\7rlSkLŀrOe7S#̳mR+ߏ]8Re! FNLii-!F`E.}S j"sU>#^p>kFGL灜}\sZ ak.z ޟɇKvP"%ܛy1{m)1tx6tC6VB*2j-LQǤ<<%> S%7i1~7>sUnm}&WMY]h,sY4֛V0xϙQ.Ƿe2`)8벫dD:6lŰvMZAƒ#̉$:vEឩ`~ l?XMI`>~g @ڳ-{RTјbIғ^] Tܶ"lĶ8&ҺҧW'uO~eJivِ5FbҺҷMKaFg!Vd5I (r׿s=qPžUsݔgsH X2=Owv\OG>639[ ۷w!t=ss2y 01m~E,):Lw$#Vh"ޞ3v`pF^1~ #}pO䀣?|*aSxSH͋6.J?=83ow%΄lc˙lԭzG3SYE7H;IQ,w-gv4 &*(6m؊)7{FbJ.T;9VB6H؋Uuw`҃1c;K9y-6szen |'+V ň{] ד_q5Pcu篴F.%Xma"v7?c3И3'nNb'/{ª#[u#Τǘ2˦NgϰguCA\6u,%Klbg?ȶy쟣J G3\~`(G-f;PrM}в&t!r*y9{?^Ї=ڈz_OO5p^W;Σs?rcN@BonWI}mzVR:3ۏc0=biLĤdPGvbS bԛSR1h55ù+(ˠiĚh3/˞C+p10ttsAJ2\mك5~0hwG ,o:=<ğ9Ɍ_W%~s{ɐCk {llǨuǗĸ؅s섡F8 s`K$;sZ :stBG}ݣwD T(Vc< CG cg̟ Ms{{_)+m!I:20hdrrRvdVV릧8Hh4eK饔rHv߮ LE$WrW& N .E7~ˮiv,IR+tؾ|EJ]Mv ɐF8~# kې7'I|a?dN;}^ދ4{Yr29[LQ:$$I^oL}DN u5/d4 b9o>ŻKwR`ukl|3W 'NSO~rXBUXKyx68N,y;Xs~<nz:ۓyqVpWex 0hT9+ᐛ)5rرc.}wJgQ\XZ"8y=E$ FCl\, ;;67|(,'M.\ϒ FFXx[|<췽i::uoZ-RЉ&+T( MF |6#c: 1$'غ1D2%F@ 14 ϫ)Hvr82Rؼi9]H"imb+O #9 pf~7Ϥ_K+s PЧ0by-E+CKdjhiE~=gG k Mņx}KƵF#9a&V?P{ hI8!L VNpDY h-~8@t39$&mHt5;|VK?: MF{͉AwDgm7V-H@Ьai-^ =zhA]Z 5q 4bi@[@ he9gEQeUoX^VE Q'bJ h'ðv0\.VGWjp$0դOt #ڲ,( . EUHzFV+(8Iiam]sC rvRTunRR\XoZlm UkiD}nB/260vDĠpݔP‚dFZ hZcii)iY-جE٬6+(--m,PX}XVp%9Qq.FFV6ZJ ͡^KL:uɤjvכfP:ʊWpՄ4k,yheea5HRk 򉊊,x룒.RFrܔӣWMQ~6ˍ"+]nl yH݇r.W9:[Yvw"59*CrBZ?▱Ym(b۹ٺu+0=W_OO|Vj5m{{**(ǞZ-XQR<Ċ2T [7oL\l1.Sk u&Ofw\h2;Q(@{=6JGV,I= 1EQ6m[nCǎ7F~YVpp;R|&yY?Sѷe.n7d:(޺94Ŕ̰gpϧ,;.cΫcJTƕ)\9{!*3;Dc>n G}2'ӍY<4nGc4(mPZe'%栒eغy' 16%mڱKpjMĶϢWDGY`-@3h0=֧(+%J2#;ټN4E&!R =<;M) ?k+C~ _ ӞIBR?gfVl,'Pz'Elܰ ~}I Iy;^#%!nZ+ }G% a/9ukX==r}ќ'ՏJiI ?3&L **g޼yr.z^~pY,jWi]*Oo$}% 5u4_EHj)}v]KIX8ԇZƚw?:<~=}/{rhO8Lؕ/s˘TO%{w޹).`9 jLͣOxuR-1R[]ެfSdRbcc).̫wN\l<7'>66m'y[v2"/f:{Kê%1xLRuTS,zdexkLS_\-낵ԛxFb'wQ»F%@tJw8Y]1 ք!K1{]AIiEec;ѻO1n:DEz6&V0K u,7b4EbbbXj: I̳dgutTy;։у%i$?CŹo!Syl("d3E.ovfr= h98񌝒F=g,;vp=1<=Eۧrک#٬U.ȺM`.>;dz6vƎyj~ο#}O^)>ƈK}Vfmdwlh/{J})61W ggGA)ItgWR3 vUQiI_qcsI `Lқ\yWى^{iB rZזӐǂ5?Y9n68rjh4´nP6d*reӂ2޴5AƛRfcd֮U *{]D^^;vV$Fٖ0k=@|*ggs-2錓g!Xǣ> Ur8tše k:0S>?)d|_>9|l ͒oydEHdf֭zHޑf2cM]G-t`9rrۖTEՠLT(Td?&6|Xc5$mwбѧ(8͜NPMD+wjSe*L| 4~8VC~^.pTTdEFvO9쉼{X*Pkgȑ#贚iK7fra,CIzk6>_N1jZRyZȖl2/Owa `{I\Bl+bxWZ!W21/f>2⟧&kx<]uZ((**`4U&EŔV>nvaÁ"nmhF6E`zE]m(u*(n{׳$:DJn {4Fe%QO-á(s  u 'oFؑQqK9c+bQ7`r54fU5 ]ѧ%<"yØӔn|H YMLtTÙZGA?)9Yi7G};\3ϙyky8d筛 g̸br G'i\p5$Smtq2]E"D/qI FAN3LȪ_/Cz:A44To;FLj6zٲl7}c7+mȔ,62&]Xq1g}KJg}B/X }d"z';A0H̖Kۡ>Cbo:q IDAT{nn[ k֖ ռ9 ݒŎ;9ٻs{*M@Qmܵ KW>kRܣGa_$"" ֍F!6>?LvvVU)=p={r4/ǞI|l Nۍ*HVh$K.;VwBao2f0I"hi4 I,y滯Z-IR<_jPe2萞CXbJꄩMtT)))a2B2, uS] hsUDmP >7,IDc4HjaZll2a2}":abk'b>Gl IX'6NN$"2E7Fi.U ' dzIl@\ʞ@ !R ꓑM=7j_ inoۜFu ނ7O\]jmn,@,:AQނ7JgsY h>a= ,3ʾn/ 2.;H(zV{bl%h4KBKVhU \.ZN0 oZ9tդqbИ7yYQ˅H^Gш܂wj5v5;JIQAq F]ٲy]}@Ɉ8eݎ`$]{tnJKKf`2f|}KI ՂjAQz]e(++nȝu%_+! J]5dFtBPZ\H~J t"#3͆v7s\Xy&і]kkҥӵirϒUX;-ϖ)e""""(*o╲2-Z̄ gѡSEEDEzZ)Z!m>NZ?AۜtAbZ(*(2n BxD$?q##^}=h@xW.N|"̖46'.o0P1( v EQE϶ suwc>v|e0m>nj(pJ>ح,AWݺmƉqB9׼\EQtmF7~|Xbe[M*Wp4֕ͷ\ Gp =xeS.v]N;Ds! c'v$rKgƇɏM=0W+V6}r'ƒm@Z^GV?g8ؾg]qמ'\۠Q[`lvt0 Fyu+?|KEY) IjlvfSP-eپm1) T!ۋ9k;MniDye@t0v0Iz6!c])(w!,2nd%=ⵗ]ǘh+`zv5}oχ"<8/ǿCâh8m, y=vᕥLy}07 -3iwۙ~s_:۱~mn%Պ$AUU,V 22(+EU^***ضm$%%ҵk7""X+Ki{0M~\CVoFC;ؼIũVذz7a1{4zWmmfFJfYN=4n<6 *I1qQMƩIGwNΖ`X hhҝ+C8=ٌTÖudA0OSoԅ?X*Z>oPRRʂpք ;+z=/ fdY_3ϊ\hc}o>W{!4X37'qtPHiYG}8z,#oN.9ļ?qN$%nm]2Iא,˙va ? TYawe8˲L~~~yyȲ\U[F;Pd:+-5оM R؝WhM.)V-waizj]:ae r96#Ðj1.r*FD`+ȫ0X# mdF{>V9 S=rO#BڜΪсn__c~NgyEQ:r6*o~G:2(jڄt֓gLT OGΊ"!Q:qtIDԊ9X)*&A}>t >lN&>tLK ?VGCf v ;jС# סc'4Mh$v{UZu \ "Z1"$Q@P7Sb]o@+;q+U0cp8*8l넌Gn7.|κ5%RU"_|J^;ǔvsΤsꫨ(+NFLF,#wnvl]s`?"^aԺ:jKs}TTК Wg㣸nVwťxB[ZN*TR*ԝ uEHb"ewݙWny̙㭻li;`0ӏ[~S|u35/$i\Q,}I7 'J CBΞjXjWfAtit7y6b$k2t%Vt[zL[6qȕk]a=InQz;`&TIeN4%"$G.ՇO惼<eu2 |UzBZ‹/`H}:o:/ʟSC%D2K>p)۫xmϜG^l `EIQQQU]hjҡVKc+h4QVVNeE%QQ$ɧ| sd]~NqRk=*%o̤Hl+MŎujv#QRDQ[ߎ|ɹ 7fSq-*-MNs0 np $D ոdPzjz=A q>>]OX,"Vk$-]aa*F3gRSSQ[u-VDI⯥KDE0 vdiv$~2IH$z/_ՙW|_ʹTt2&fS^,^Aﱗp˭oꗝ>`ȹbGT1us g^~M:Es?eDŒYI!h>.w__*`L)Lӑ|>d-VY0GN; " F\κy3F YY`-#6,Աij~lj"Dғlcg`$yaɢu&b ynH$r7@,Ǎ"Qp2 )~ nfֿX5o> %ȑlڸ_ALvD@'0`ެOuOaEn͛pyy?d5ڟri)ރ l6EJJrJp{Jt:zjv8 ѽE$~[z2w:Ņ>@Mm ;Ɲ@\l.A:"f*XpIddyhh($÷,o:^0|ڇ$I$53&232]y?QSS4Ԩ(SIOTt/>;f4_H `?#>}UO"6m@ue%dffixjh4v݌0^ZoaHhF#I I8k6h0 4rAe}wAJhZ9 A"''krV,_/v{XOB|fA50ުOu]ݺ,$ ٌd $PhK8GFCEV+Fxu GCz+gɄld4igtkht]_B8NK 鈴FbXQTWw1544:qv/ z+㦍r44$k +FX9 GW 9h-g V]kht/?{q)UZYCc^ w[ f.Ԯ?"`APۖ[ځRA!,xtHBG$\k _avGl ᰳ{W>nw}KCYQP/c "zQ. .۬sx<NyiiPQ&>~Zq~fw7cΚug YR_OQcPNh Op,᮫d xw/BǠS+%Y{ Ts5~ E4 Pp^ݟ򨍈 qI䚄&Im>o! ֗wxIyэqr\ {Bc&.!Nl2y)C#NIegZX[4#9~}RWST,uб-O~u2&%;7Q-;Tg!+VT3z q-J yK!Idї^XSaq#H"Lv(-*lE HRz_zd%?o#ۊW$L1Cj<_T2vn\ HXr3rpWna" 9C?dR~}hxw2h9>~ySYkDcglz3O~HjSmgtf0R J%(gCv TnZy$S3uCɰ)[~GޑSj7纎}A_^yod 9jVOf*îr1׬GO{2l<~Ğ=퍥v3 xK[`.؋K'26wS4y 'N헂n#gotlՐFmF:G>ǐ;DcEu6lPW[Sʺ;͑}yj)ܼL9 co+8X]3*":A1|NvWє|HiԨN7mŘ܏#z[eA8-rVV)#&V50aNوЋQ}1⢪h \rp2b8 X|;dgx,:gmwS؏XogVl8xb C~)t2k'J컏OtC6룒wY;fx9rhɚ$&]bpfs ~Q[yIx'OS7Et- 3&A:)2 sFX3%)M1L<}C'J$6p̈l (@nu;Y>ovU!CS>lkR۴@GKCM(\u:deEFrգ(YQRAJos&bNJź­QT"B,kBK`_t,wQL b%RlؑBY@u_I ZI3v8(HҿYCVΒDyaw 69$XvJ#Hi!MSag%*n`8#LUL+k\HY IDATޱwƂ/tι&j_~iWB˦O3I >lRoKpRu{cL^٢D.Ao@'; 6]fFZۻ; Pęie/Hzt뢧YU@Y_| ʌ'ҨC;&*\&Fp'Y∔"wj9AUPPUTa 4T֡" Ng" %K$h.쟕lvh_b,.aܴJG$qdEmf;l $ *TViY^@ 0HocY M?]bP_?at YQPW?8(-L.҉s(ќG]7#L7&& ṟLaϮmlٰ?wCb7fdX)XO^rh!`gOuV/\Wչk 8*9oU 3F8(ɨ$ʨ;_6AN#{QQڼ|(DaA!ёV<۳6"Iz w"J jKS;D"$fbvR6I]i {d&wp)^74,MQTeBC2Vʠ)cʈ~S!I1*ӑ2܀!)|*3(/00AU0m 7FoҌ {TT7O=b"! !z׷*ޢ^dYb Hxϴ΢(^ЮYt{sw+76m~3h"PRzҸl;>ό &κT`/Nݧ7c Z1\زuc| Bz:W%^s[)m>;]Qݷvnt"+EAh49oG9g]軭 &c2w#VRGzfnXǞ"OEUVoR2" ȊJs G0* 3CN*IPֱcW ՆTr axRرӃCLaE /-r'~U@S#OJqE`%|-$F׳NFnTM~%,cժq <1|*(J:fEOg HII1kTSZ(**BDrU@uOμUl-RջX͏e&Vu ݏ㋲a3:+&;!W\Dw^]o蟅nd>cYo.dKu=nV o&74=E11Ν߭ÚofڴV  m Hd21!`4Q^YIeE%Vя=1H;װ:qRY UfL8JBd;QS"j":'U.i oGh%5IdW%8Bc\ĥYBDz&Vi"MCٶM58eYu( 0UnluTldNXtBy#&7C,^F8ٲ> mD 10+XO7(]eTdYAD6bB(IX-V-Yʸ㉌|GKd^ϲKXFe/_wP-hMeИ3[/8bg? KK9OFs3p1u']x }- s}t*r;(x9sޖě|8}7^{^.^A.ƛby Qb9D u~M&TA`ya1[%tزP=QIc`_O>IP1oDF%!}lmt#YIωc?Y #^`o1c%9 kA9DĦcHkqmܒv~%lٺxL$3'" K}zڑ_kWEdL#|) gޱa) xZۛ}ѳn 8sXFXN~r ]XH-.؍,]veVι"6[NViϠm[Ç4¢"V\^5IVʴk?gbRʡ7KE+z0Y)**bĨє;4Q[[Ν()/ۃ:&[k{A*XT[Zf2SK~}IIIh'M6RUQIzZXfȕ@QAQd '/ ) =0z{Ddd$FQ| &z.\ʷ_a[ Ѓ(DFF'2BYY˗.p 2$a1>c6:¢ bѝPUI>[97""f  aCvC #"A-" RtRp6U&шhj:`~{l uKoc6dZ%յ !@GwmyUABT!(F$,+f5}۽545TUEQA[;]zV-g s@ZFk]8[88iںCetkw5>ʐ$ ;i]B;! 뮽xEe[:ʔ 5/а,Q ?Ws8HEYQUC* gw}ިm_xMQPYx$^sMi4 k!3kF3 ߨMwYK&ƿm?CeU%mY bZl8IC>4?GMu5⢽X´0>ʛ "JjF3(zٗ}#{/<]k_or:;*,r:QJxAHp\Lv77n"""FKzb{&ih]Lt1F¯Cuؾ7_Kpb;Bn(ىc)w_d4 '>?O^EP=dM|;|?ʀ~3 9k|st6_2FE1:7ʹq8ېo~]5 ^}5\;q@á )x ?.L )C8ktl}TӇ(̜07n"3+ '\h|6 ϣ0 2>׿bZKnsXUϏw07Z?ocfх84lwŷ/Y[\H˹ ?^2o^ޏXٸ=^JiRίKq5YDGxkJO ?=0s?N#;ƈ(e%i. ge姿Q2Z˭X7tK?P*ǩM?=}Y1yx<{OMF#`"^}dbQш*u+~Bcn~{+{pnY?r׸cO11kzcmaћO3sv 8?'Ť٭+~1o gǮ 4vIse *Wa۟t6ds'- (v*M]EGgZ\ɒ])yzǙы"k}GO{nbQ sQ5t\$qƄt 6gM΋W d$xvzN?tz+)ۯc1@;cO>U7̝;^SO?)B,LgvAYޛOw2lz{N` AtH=)\TpnY HݚFrˏsH01k.$nbv8P㪡qв9k ln3`:\%WkFOt!,T]}[H8%ڿq8]ϹEP+v%:Pٜ<6VaP+%nG^ .lnV xчfٲeKC?BNv&Ng$/B,Qil̂)w$ Q:x/"=F_˵ _ ďї duX|kViOĵmy &EQO;H544ڢ#bpc0Z쌸V_B8jV?PTL%k~xN:2H @G3IS(̄'G`oatrUNHq'Ǚgϋ&//ԩcO( 7t3G1 >}v⠢b' o<ée (F]::7<8$/ǜzu ʈj!뿣=JnQґٴ7>44 uuݻaoW0!,XRlBa$/Kcc\_KI6lXÍOCR;}؋ҩ{.XOў҂M-ֶFUUtgs1m=x<Nx_rՕEcdUg[5Ԗbkz7 !#=b0G89ϛwݺo]@e?qi x<( aQ^B541&3Y9=ٲyBnc΂4X=Xd39g%܃?~~1!^C3rn5$^{ʠ"/ R~G֙'1F;cGb/q3x<:{6x<v4*GDJY?F>3U/WJOlmTj~ER'\;jZ#%_=;-tDzm~F\P[[KJZ ݆(ьf4 4jjs%rӫi#د(QkTtX5EEE n , sֳcúo^dTmwKXų>{VNoQu8f'sF ,9"͗@y."+ګ;D@Ʃ׍a+YVLfLn!Ux{Xs)bȶl]9.eN\)#SUUbαO jF3q8DEFRUUHn}-EIjlN?j^%ZM|O5Ɯg2:n19}^My'2T}:z锔1㭇zS oB>TUE'(<0A*Km(>$Oƽyz^ Xqu*Gl>EO3b(&^|1@]T+軹)Cp<X?ziW3G_ͷ$ֿif#xɜ ~UIPU.'ӓFxBVNEUE@-ռ=ny_?W!9^3Ϣp./ζPÇ4¢"V ܞX-a_rS31knj;QH/>gϞ&=l¸Og AOdOӫgvM3`0>~1$ttjp{~6F2]P&(IOKl4QpۊYݷTb7oN}DhyqPh~KV0{A@bb"}fH+AaYg/?p,c%!r`:]A)p〹443[ -ƜEɄh$%9헇y3ǡwAc)'ͤ1ߞ㡸avWwCݑ%&t >pT}bBINK~=,wa `t1:tWKdv0kht5 ^:[#mo?\y#iwGv:m5=nխOp,c_3٫>e3Irޔmjэؾf¢{>׵<^YPGc'.9u4αfxe*V啷fND0⛸ ǿv0L?__R-1+1$;i|x3J4'\KjDc:wpo_544#2w0vV -Mh12/Tq{̺aĴ5xo&fMK l\>Zic%O 1g^3Dž,s7bw1}LߌvpS+9ykt*Bb^}d&/=FEohyQGQYk2g}!yzr6u}ieLdZck{P棆F Xݾ Ǎ݃riI2Л9촛w^EzKݤ.œom5`ˏ2$uqDžUx n1YDD?9e:f=c{kaNƹS`Yc{kaJʹSvu2bKv>hQ9 ɍ!qG862vg,q>=jkgo17q>X_p5m99N"b]^d1vdcIH4l~\lq?ipsVYx=$)9# , - K$kKa8=;f[D3MAo@'.*v0aۛeMHN_:wnt냺8W~[];E q;ŵcTbl8,R{{!b"|{ 8qB[y >Ia4,:AK_o8-q? auD p|d(I졬dOP2}G$dW>砫lŐU,%zT&Ţ?i󿇢?~(2 ^*YΝ?%ŖzpsB "$6 i632_WUމsokKWΪS][Cfn/쵵mȲl58lv2sr+{2_v/K8h& =fLΉdy;u u q)Uo6y_Ӹ~#oy^+^1qw^Y1_0z'f2:oNx8az-1hؾKjT69JQy16m#33 Nl $_gF~6.s̹\wxrc8*{{s]`Lc31ye74[44nZ^htn7C7 +ٳdbbcDuv?%+x^Xkyaf`l?B544̣ SLRJjoBҼFT/XȜ9o`"f|h/W^yAx` 2p9$ !&u,-~g" vpqThh?¹ teYniY]?[[~Z6*g1_~yyL~]7'|EQaT}8v[m{,lx^s"7Lt:?>_raoM_(]ڮW444B'>tƞ0=^ze^xQ Ҽ"Gq}t83<{ vqPugf\74io&?{0LQXPȼ6hQ$%$d)WC$2* FCCcm NGvn~XgK$zDGc2HLJ.,DՊ` 8|ֹu]g Ls]Y~DT5]_9zDɌh ivˇN sCx6ƁD8kgw18'čM_CCCCP,G1}TQdY* $)rE 0r;>6 AwҰ!pO(9,R҈NPUYɞ=vL;44Ng+n~CKCC-snM@%PﮧA 9TW6'"ח:6n@׉;c9M_zQtxiږ@u/pRuUYC'5̈́nǍ`ᨊv<{MNEY "0dlw,+gc_atD^h7}xauTV3( 556l$N=6[5 ֯^InVIm~4 #atD^8oc/~]. F3FDV`Un6l&FIrI민cwu?X}S㴧䗍pvtX~_|_rcܹWpb߮gg&}-JmD}Ɯ͵W#GZ˩5>mb,N.9Dy'xBEqqUǒ_߱| "92!֮?|T-;444:Fgːvz폭:y RپuFw+^.eO( n}?dQeܰ ba[h8|Hy b] < x|g\fVOlu0gUnexdqnRs> x_B-#29vDG$%#c dW&p#`AB 'oh4$t6}:ڦiVVKΆA2^۔9AEQ:&$8u(f>IO6CݏG9Du}$D(v*]&Y F;'6Vo֌纻a@z,VѶ{oMd&$r'".xTTX|żV0v~">|##/344*:Y@((,G_IN 0OF6ÕZJwu:}9uAb(#18(ɴ;kr̳8"Yг.HtLo{&iׇ254r|D1LMb"&6nl &AWl HsS.ֻ aïp$({Ԡ2|C:GfoquFzYD' -b ; #&"w}!_36]MW΂мY3LMgʜ$IDFERVVFUUuIphʊ ""$wkxY|h ;UwnV̑,%P[m߻g[؉ <~|?_I~ d㯼s Ç1fsTv+;jue^ٜEfHqKьd_RTU䯙OfswjՑVjhl1YHc8}D F&ϟGZr ŧ8B]zm2y IU39_?tlwB) ׋<` tPQ~ jfw qW]|wx-]^[`~eW=v=_᳝DQ: 5j;/ٷ70 ;TwX{u/Q?sr}|(0xvf z979B{} }+w } ']r=sO]Yѧ(0h^/LS=Lai 6nty sߡ,6/_?Y-gÂGL{3M(!jv|*jָo_"+CHma'󧹓7i-6O!V< SW U?⼊1J~?gnZRl@`O{/xBx?۹ar<8Bt'ܙ$S_Htd(jB2ٹT{}ʌҀ6]ˏ1y.6y#OoO1@*>.;a .+W]Ə/×VRLO﹕Iˆ3g\y9_,c[LJLKP>Zx;{O㺟^Ũ<+C~EuVGn87EKk۱ƙ!"j9th'KnS󫍎S;z}"U9 gYKFFH$٭Vރjx"t U~o2!8Ķ'0d連iSﲵ:D k^6Q-2AV dvb5IFyT "*v|&0]䲶}TZ;wq3gM8'}+=cߢ /<Ϩ=nPR6WAZ7CbdXVKFkqQaF:&G[ss(..4^5rr>bt8ڻ/\GU!(..N qWT+#8*%SZk5]IF'ʊ V~>~ [p]dge1|0rsrqhj .bvBi{_O$!KwX,D:iin;99abt:q: _FWY.lV;4ud}Mӎ;b'}X$3fip Z"0ip2;~Z7u;*nw|$T9 ŻZe&>/Oo|^L`葬 232jٮ] Gd@"e6A-B!v{y5jr a;B؁_Nc6 TGrX_^gN`q8c^C4 Tlݗw0l\3kz=]W64K4{sC? +Re3֥S)0T qߛ=†"H6B_74r3ijcU)aèV=daΝ<ĿϘ4i"sf_AqqqZo5Eؼ P<{=G޴91 N9H`{p{9s'd&e7PE?ue~`|[ypדI2Gu}˿hԷ4h%+Õe8kYx?KNR;pֱ/㮗˸Rl YNOO+I&:6uix5dg Ql|r>V}a|Jz&˃9x5?~eSȫw]xO(#n'c z^^Ie ]靷pi#sp Yf;BIdmʕFhN Pia IDAT*/|e+9h]LǑsi נ Lr}G*egGG3y^c/ei #߾)[ϮYNe 8T,Dв+ICC@ 4ݥ N?$+nݢQ{^K3ӫyERGT@w&sz#s8͍/|7k2t9nK%VK"dEXYPRu(epZ,ۻ4WíQJ)oaXl'|^{mV}bX_bk*Fbde arcw+%J)lݵ\|8>0?rx Ưŧo<3:+Zi[n1 BAt9NJ)3m-~[orƍp0{K+YjUa7>s sR67XupɦYY^NŘ4]Ǔ桲j/yٵm kVjk| N'UTVVIzU+q+=8T *v^̣!<G:,JY?,SRM22!//wz9Ap05 K~B<rsWO=̟~Xzt{|c|#Wsn)^Zo3ku+#6=;v`_$am̺z.[~/26_iqBB$b2dH^xz>lH$–m[9k{u}.+*8e)dge DϩEJp(<%Kc@i Oz**D-J ╅/2xf3yډ<м1n8s{4C9MIQ!Eddg3l0a ؽo!(.*rK=}G7rY[^>3Mx)+IW4:n, FnN..\>{Nx cmo啑BCF'͍a'7'@ 0 , .ӉWEyIiE YjcN (Bt %9C#T_}J5_9 IW'snKѶd\c„B9'rI9 ! /B!,Q(:P)~Iw9sB!z IV*C:$D&!kib&p͠6]GץGL!c*‘6b\6ׯa݄C!l)z }YQ'ԡH$[2|(>f|n-H$eokSBv?MjPRbFS׋KIi)at]mߺNE|ݝwR7,Q(:Mao5iܿ̄߯j223X,ɤi>h7cns9'>7h 2R>T@0LR$ R($ a*E77M&'q>Nhm!4m?苞7?eĖ=Ϲ9g 'S-́/绪`%d,3.+2zbK,'hb贯s텙-Koo㼸|3a@F}i_=a'|g@ ]/u_YHn|HM㸜ֲۨ52u#l UfCf\wgH>V#`.x0kzrVާuC,ׂ$P>Fmj?N)0ѣl}ufj޲O/oc:oӮkC뼍cЀEȺZ~aPɆ/j6x;a gds)_1,}~z_3_ ι]Vcϑq]ӻv?~9os,~wE.:r)ws\31?jϹO/;p3k57DR7~Nrp??a̼|VܶPjE&YwtJ^.OuN3u\vױAhVƹYo-wyM? +ʌ|;Eosxr?cop9Dz eXZoYt]0_rY[0Utĵk1khi(Dk2i#-M׭[Ft7nK˸NFMd9[gqe *bb,ui{O4j?3;k~OzI|RZsuO:IϠNdJKd=h1me3qx<6ՆV暑L21AY8^\0[OKexel;yYWA(J|Ҙp 'n6L;?_>L)ab%5}ΚiG\fU:BZZ\ɧBFf6^6Lf|r,a%e,/^ GabS4 yK'O$l`ДqYEh6͔އ?| .*s-oFc/̋qc?ڿG∗fer=<AXt,g^pÜ1-E}Q*_ymڻkGs;E"nƬeD"&MӤLȁ}{[b!_V)99-.oIE!Fˡ1\mM vBǏ Mbg)XZ;-K)DO`Ze5ƨo6N䉇1hbRXRaa֑H譔4MVWf:nسoo/^k3t| t=B\(]O逰4 4Sg!:q[iuM4ͺ[9]HxTZB~N6@h]N4g4nճ^cƔDiJ^:5>ڦY7Z[~m !&9>ZT&4 ۍ"Qةsk~ z!ml)jKluzدm!FFcMPG ǑG ї hַΗ>ge3gBta>纺&5N>uHs~{e1! Ja&rY4ǰPzeUw;+sV DϜ{;5р0@. A  }I7-ewvƓȁBnMl벶2 Qa|J;& }BtR T=Ӕ:'D٧V”=[ӢOBt4дUXt -TBĴU:[ J&$Q[; щR|+.uW!YB!D#BäTLSap8 !ɲl%͐"V2\nW/uOOOZ-v!bLYLMg!TWs`TE-B;!CD %5"IYz1j 5|թV!h77^qZRoJg0x8g7JEB!Dkj((*Kzz:=qVJP"z%_!8(4h;ȭTB!Dh){BQ)n9G+BK7YoE]o1'9W5ν*~ú)N< 'ͫ?#^B$GΜz #ɽ8WU0 6,V>f.,%oxbb.vۺm߮ԝNܸ{p]gɴ#Sbu-]ƺݪ'h"Ulp<8ݹĉn^*mJ},RuZ[Kk_jpsip틦wo< >p?lwM;\{~4:?^ϨH+.ͯnVݷGqӼKm>C=+R0eS)0y~f zߟ}j'989 S\nhA>1 Y[l2~c9Y|Ē#uOZW^_4WF7.0?|^\;`%t^8/|l~ɷY>m7gkb̔sʳG8RXHck۷il5zTfIs<3^s\F9os7K\o1eY v|wy cJyu?־73^[n-y?{n3f%+}Wa}O/9J;; ]˂ʝ/䎋JuSy`x)=*ٸ9yn)&-F?*ga7EoiUm>Uwpf.,M|?ok %dPO˼~KɭYŇ܇oS U3Noq"~Jܵ퍳Ò)]CG笡duy,.[V-ona>}~ o` edmdk9d*N>ݏΟ\v3=Ǟ?xFΤ2uj&Uk9Bm2Z2DA ʓ? <څg8-qXp2M4}:k$|Ɯu=7[˂?çL8Fn`"2liά\ɸ8%feiVj\>uN 6g6O&7^G^I?Ep/KǗϐ\'v3[9h9"mtJ"lV]q&O:wJ5=1& HnOKuTxdʄ<A+_([ҷSﰮ"+~0;Ec,/F|)‰$N{)'4Zlɳ3u|95۩)Dk/aƔ~X̷dm]ŮPM2[k9j`β yL$L 9L% O{|(AYz y[8n;&S2iLd73&ԹSuhm'}A2j:wrO5_&`bk=,aROaqIX)>\e>˩:~`,$N|$NEKo]bj=_7BD틉גF" ;Wh5@"鯡2fBbjNIX27 2Nݙ3%h62/Ϳ&?F4 HY4f&Ƅoq'g"M>6K*IfP+Nύ׻(U?Ɯe>\p;?Trtsc^w0"_ô2ȑx8I?tfƛ_5j1hhVg@ik9XciGVca,5' -&`Oǡʆb)p5 Ukր':#$}h$8&L*`ʄ/CAc8o]A-܌>D%[Ί7QzI?n4d:;[bG4F}.j$$~{ST݄:+]fqg\A9}Q,&ּOyX-!h@>R t$M$dLR8qYΙh|eOL&]z:ge[ Dٱz1wgm~Jfբw(/DH<973]"Vkfק!plٔYjD Lzя5/97+Xxzyߜ@`t]z!9o >aGuH1EAe bpϿ2ɴh`+3 X5QXI4Wnok۩xN)4&}ݞrI4IK:qJT^7(T]G~bmٺOӘ?o3x g_| p.>o6IzZFN9o8*il_шnPп7^{a6=Lv"O<4AX J"9s$\so,jEL6VuI}E8YS=F\k;^wk;k|{H7rRDd9sįxɬ>}3^JhΜSK릑oB!D{h}bvèC!Ku\m۰98\n)4M>S>S>>M  2h qۭT̹e;srR(!}VeE>|ÆIoo9};wd3jhiiNB>_0zcY]ǼJt Fz B!DWH`lX&F5k^WKTqt9N}rByYٸNb?,gκݸCQ4ѓqllZ4lxkl)d1eӐSY&Ga%d SN=1Fۤt6u*xRV6 ;+k;VQ/~ҷgj"2wS]&͍jUVKb];vga&$Z6 W_Fs&8i9&S']}6ڧo-TM~ |*L,K}uy_gۮ2wxS#d{`+qwvN:gFhNSvxmfoiy FqI-[WgpgpM/_ӹ<쵻X};PQvݿsh!um{M>Y#%Y: G-oV YS0g4ܵ5k>bπ3`6JOdC:YoL9C ^[X7gF ._"͋Srx{#frr`H>"?3l|4|]0ITIqkin)}zYh$UV::jش99k5ja/2'daI$mm @M"?j3IɤM9c\*3g }*3*>_:41ti6yyL^@݂ŞN'sl`5a1ج3r~*&zE:Ỏ|+8}| v+bϘg:#-YL:~Xu+a9VղuX~GpӋٳr#|WQYz&O,%aȤd|}r˷WI3REuL(h07$g M3E餳8'2NF2b2*D˦+0|+ٟ8~;8l-ٮ؎;QIg" OZw;].g%\ ZW dNEa/H%[6#=z G`ҫpO:5óml+YG&*#C(%Nk#U׮|& 6KcI1Aq2IfF,DKV2uКQ볆=wx ȳt`_juV%D%dKgiXxCb3"v:--5ᱷ<4l?ʬ98YzfSu*ENts=l}Ǭ3 .^y_Xc; gc=GaYkeD;IQG:ȶ.tNҝ1ZD&bgp|JR)d$/&*r5,DKV2֝dƈHЍpL@[g`;&d$Ym.vv ɴO]JӰZ#0͖{b!57`+NpYB&͏.fՅMs93DvNeXƑ5Զz)KÚ3┈۳<$MN+K/8{r0x-[=8dҩ|#l'/vm8H M%`Gΐh~ Q48b_F"icm"ǸD0bOn!G_Ɣeݢ[P܍d\eJqΦ,} _R5VNRDLݳa׮II7O O%c:Dk.||)g*FgpxZj % L:$4!|Xsگu-j*_Z'nKs,]/&RM0wv;tHS򲽢)LoEZ";s#_mL2kLR8kk(eF ^?Nt韽īl& P{-!@0x`² a6,?K3)Մ5Ul-j\o- x&r|o`rfc/7݇.>坅`"{.Fݟ΂6&8ցt&OKjnɇbb]q].͝9=e/ͱtžH&Z6)DQǒͨlyc&l|ٰGN܁2qtվHH1ڦ3jMOVL55.sr ^| 4\Ng;1q`̙d$oylgGo?_''p,{߳qx /.>H3a8PϚ?.pt+g  J,"B@EO|j5 M0M՗VWWJErB&h2͆,*EκnA Mv:l%c\/hDB¡7|}Gg=SvY[5LeF 呙MuaV}AC!$B!F(Pe[7m&;++fT&żS>S>峗}۷?x#d@i)nCmb!_λtQԿ}ʂ|B#{.2F=dm0=#웊,AF; (3Zu+#"U"V2\ (@#ٻ{p5aP{"v>)!6ғ< e`^b!1 4 J8vwngСlX%jXʰ=ScBAj|nHmߒmw6퇣YCI#z1j4P`.b6[KVVVuM in`OHA-(ŬNny/ C!(a^ՇYTW{IOOִ_wTW|yR00~<ܮ0qBRBPʔFi2Q40Le(e&.X}PL"899ga:eY, ~;$Kg!Q'tn\} nOAv5 bǓGai<!VK6&6jnn("6޺Ky?fl$oλI?LChF _Puӏa$P<YzAàqS9 9wr=m5+pcwpBZ۫+wVO!QTS皚,]CXۜ>Tl6[")!p` _l%qṵX9G3}ⓊW)(;Y1 wtyLIT fGh_n=wR`s:(_-[qIJK(-dԈml[p;W}?4|gWS335/LRLEb'7YqQ ;gɻ;v,4 xSNwMoo(JҎ [l. dwJ\-2MKr;6 +fBdɜ9wMe.?ȶgqֹtdΙC]ws֯67=/8rRe;cfpխrKAz"uFqBuTg6nbӖ+` k~._apXF" w,F[r , ?.~Fas[SR @]s"|c@4̧:|g>{Эv#{M\J5~miZs%I7I:|1Ft JJK|>|>6md S1joރ۪zY,Ylq8 By8qK6ҔRf[BwضΐɲC7l.ieR` IHBƯmI~ɖdKCv0)<O~s9>Os=7ANqJ1]ig8@"SgbZLn (kw#@Dܢhc#}oûGXG(I66✩N6yX-ʚvJ{ _.3+q+<}5;>dyy m+d4{ojώs*羝#;',wӋֶ0~^^?~H4F, 㘦IKK u^J4mؘͅgL)(PiŽ\,Lq~VY>ڣo{M۲#[ 0L:}򛶑Gf?=HH!Q{Ye\ws&-Nw-LLoaݧLJTDGvA*r+z:@\RXdKl<}-ڇw/W0Q۔\l lp.\EhezGZs˝jhOV6f5%t&%8Y&BQ>'\N4%`%4M#JH$F]K cSϐ3?$݁bbBײRfΨjn|3ܳsʕh=l۰Q_u[0]t0~K{΁ wc s|ϽdZ묨 R財ʎώxt!kv'",,FgS LneiWTA2aZ'3Ȝj׬^'RVVͰ:cctv-%@^u[џ"tM\-8nst47Ӥ ʠ<2x[{bƃdZ{cm87B+۱}3\^b-E_xmaV s([rI$9x[v} J` ^ԻYp&Jcyir/l NPXIu!샧!ļcY vj DQn75() ]*FuҝLY& \ho<0X ͽ 2ys ]ޫbPUXV7ѯ7R~kWy\'zXZJxjz]\O^b8'KN%J3jtkR$-56dq&]s~ o VWxQ~}P5q$euFSSNbͪ+) λ>lĚi$1F&NUrXȜ'գ{o'ẠeewE>7,by؃=o_ +q^0 czn7 zSqOa߿=e_Hݢnz0{9=쩯I@i:{/|1ZSG>;戅>MjP#Dt?=O,[f kBy''Tzδp٥K( ?_R-N l^Dqqd _4|X\'OXRù\r9;8p QO/W<~fge98;;ۖ/o9a23+Ei2fZ_^* ,)ŝRZKvv+J!&$+$8&3l`>".ǦyB۳5ڞLii`Ryɂ;9115`sp8GQ3ڦ>K8cǏeefpҟKI Gpuu"C՚ *>Ǐլ8ߝ>#|߿w/!1﾿v %KBHl`6 X¢EL=z!o{Ri5ƀ1L{SLQ#G&JL@͌2uFAQZ28UE\ͳs?N-{#6͘۷=xܼTPc$&&z{a^郷xzuPg~ڽG#G͙;omlfZIȤq:I7K8װ*}50.^Ho"iD\ri=* *3}e|> )qOON6eV|s߾bŊ= QgE`ÏI;f葬 Uo޼YUV$Ȁ6M:g͚dɒ>HW` 2:\$MO(ISҴ6zuƧ%8ɩo=vcuw_2m\q ,Kr6Jt>| ᭍MYT&Lz[l5zȑ-(0py| oѼYz?~jՂwI0޼M+ 6l?c L>yf#F J^ݯIF_T_^f̨\=o/[۰Z =ä䔯͛L#|a0 socV.Ytk׮1c"0ĄD칳=FMLtPgBrˆwSggľ,e%ygAIwMmySS32~qNN.&>[,* χKNJtGks 81'7kl,@lv]8G)W+4422O:8rpa/*2(/^斛Z+WAJ*߹sE淢c 38 ׮A|3UпϴnϿ`.6&y0Fe:'~ÇcqsPux㈈eCkzu+x֭?..zw% Pjĥ7FoRTVGܹs!CD%y˖!C^arXu)Ju5k&NNN^߾ |bw&Q0?lj&+Wlٺuƍe6$\ڼi L""#a{XH"h $]6E.Zaܹƾ3uVo6 .ի[D :#uFVwFӝGlS&ܧr_=Wﳛ*%%<*U(Y2eQ~hSd`366F8-[У:ofB.Fxnv1lp&, ȱ5I4eʔ}o;7lt\\]14V,FS9J,@tH֙>M^ޞ{֭GBHߑ0a6Vvr% Al%alM8ߴV+,((x 4A3.->xHgdf;OƆٲ rs8$ہZ/WNCCCܹq%ngzNcD&VxHk0н[rUޯ+h cboLƾNnNnt4z,'ܸ~gzY(Ɋ+֮h_ߊk׬ֽ6<0PFPghb\ZP2-)12dodebky[/wWoOt=JD\u"N>mo D[U'@Y&dY:p [5v^^.11 cM}0w<0۷=g*Y3g񁯼{  *I#{ /8o:uL0CfOy۹Ks|\6!!2H66l޼y>|8Jo޾ lٲe˰WӍT8|LZ gJ0zI .Ʀ?y::) 8{ +JnlݺQ"Oɉ'y1)b_ TuJT$aٞq$h>uH_N׮\AbÆC&ǎg$1R`G:#gWNg46ZHڡJ5!'-=sW9^<ٸ~{[;GG{G{99%ߎ=7Gqny{{avܾ9mhtcÉ>>ކZOĊ0ǀi7lXߡCѓ *g?)`7!}2%Kl`1XХKG'G_&_hڬ[G9k׮ (bz4|"xgwzEr QPc H*TĮ/u[PZi XQF܍ݺboȑ|r^޸׮YajM0 ^0**PyU9L{_   awPW^ ̔DR")"m:x&0 olKpLT&L0,2? BrئcHN`mrc 댰>} 1xG:#uF+;#3?g<;VM;qZ5*x:`FF擄GI޺}AǖuGibb-vP]t!$Z$~O.ܹaV.ԩS˻sľ+f[ 44ǏazF@Q`)>hi`x 3~1Q?L"@.d;`ń ,(qp"N_dP!J&-zK,92e*(!|(2'j I LRu+B.fD'̢ 6x[Xl SRJ4u*ԡ*''/*u*2;gBA.˜s ;Sp.85x05d%'$2)TX̽λ U䖷H)3Rg(t8|TŭZfbֽC_ ^u#.F~Ľzuoa *wǀ~C|^ߊG9fԂї tVQ" Gߎ .ʡ@ xخQ fs*M#,X(Ûh WQ8HSh-ZEhIKrcL`6OlTl3gF2Ʊ!0L`{<؞ .DK N*$n VQgdC@KRN*3RgdQ ݸw={FGǢJ,ԡB Uz8H IQVD;:rn%̿$L&煮1Nn؍'60m o 1=v)[Շ,JXgz&hl4J@: `'7&>;<cIY]b](uv Yv:6 @^6Ph_chfҘΰjQԮC99%etYݱ37SP$Ŝ2 s0 D i7N lcZr z" +hbU\U)wFs xJU؜+˳ECdRѳ| !uXQ *D:_1ZTESxU*QTxɓTfq3M&'6Wn%ޤZ $B)M-wPg$Kb`9 ,0#gd1ޫ:z"JdHV)I,x%TS49<3fZKJ"E(YNd*F1HOt 3FM Rq$kThZ=JR 0,ׁR*A+`q)۷@D5JѲn]jZ;n 3AsxjØBޕ7̓.XIR$3 QeLa dL:4U d9aܒ.)d%J@b/TX";cGM"hjnWe&@2b>FުiMgnҊd+4 ^&AEJV,#P(=`Fz1 (GꌬL6GQZ5bgrSM)D9P l^DAi?+ΟИ'r4mlVXY`\5bRrN}WT '[h6M 3:~J]֡6iS:'Y !U]9"lɌZX/*ԖVՉ3NhZ)zir)&l`R3mWӦ6U*$6X uFfu9˖%8 ZKR)paWɈ0 %E5IAF`:LӠGERL&d@2ȿ)d.0?TB852w]>SAP3SgUׄ:uF!(6C3kC3Ah6{~/$jSS,xRL)r8v"2%4HaQ;!ۧ MTyLppe&"4`ϗ3KL) &Z4*jfxʒ:#uF2Δa sf5$Y S^W`NlBch.O. u:ƈ_⯒3(1ASؓLB)Վ5)jb"ԋmFԓ0D D&#rjZ10 6ڭJ13+:#w7@=CI`1_8T;lGm0X@DHы Ջv(HȢd2l&!٩^ѢT TR}uL^(Α:#oY:To %8,pqpp$͢Kll*>:0ɊzN|4բ~Qit Z@\2ѧi IDAT=襇F@JCcڱG>eggyO/o\Q["mffĄrƶ )USg̿=s5CsJw3(jOiTJVd2ՅJͩ+]tRoIjGlvN?5MZfFIlU2=iҘshm%%'WQ3#==W_fL$ cZBqhiղݣotqu+Wq.Y FdʬR]UZM(ǧbll iNy%=e\ܱC]܏ZGU!+AD0}cgMwpOdWlmZ%))(Fr7ǵaOŜ "]کPBLL4vX#uFOg;r,< W {綰h^I^ 4 ==bEߘhWWWcRTȍ`*1c-T#5 9QXTYIV $apcs;OO{W9 (qIVaLL 5X&( &.%.In3|U9ןY1.s [A- v25Wr3Z\45oe&`Vv2Zy&dzzfNN.Jk׉:]xGbSrmt'Q2Nڌ4i^dEj-_ϚDX9`pE 97Hd ff`BB]~]៦&Y=Z7@\=WYl5KDBS2yٳgkժ5rgڴ°Y-n.3t71N|4tDcb)5o#&u5o~QDAdx5j@vZS#ɚQ ʜX .D޺dt+e2z8Cv|#v%ʻ-oIJ-)e6סΨ䪝۷ojv8cF5̙38Օxtp@F+ӷO_D.Qi߈2u1P'zĄ>s912ј^z3@,bb'Ds̙3/]ٯի߿YfH a渐P>,\6֩SÕQWiOKZjze˄Sg4(-%8PF;p)/?? | o~_ +ݠjNN:NNכ4i~}~AV]N۵ou۶Aoѣ䩓ccp8;x>05ӈѶmvpO6h@xҥKx'^&r8IJ6+3cm=٣Ƕm[5k^l℉(YZn.& Bk՚>>Fiz:;vl OtnU{Cr%Ѱغ}{`7lX|۶nC"!!!..ΧO&|)WWjժ͞=M_~e)׮^_~/|u,{9-e^{Ϝ9n^I4dS{ǎ_ɳwb"_[L`ׯ_O^zsXX;8q8`^C5jԀ㋉C<8pXعK6n$6mܹ3~f͚TTy`VF_'o ߷w߀6m9n8m޽ÇWaCw [.a|0h@ԫ5i2>83 2øks#ή6l8C2b~^^^ go$]~f2ޭk'` 2!v@ǟ|ܩӋӉ'mQl7vXyLH'Ǐ,i;0Сl5vrH)D6E袖C@D צͳPkʴh7@ˋE9 DP7D!0 ֋ Լyado0@_a:imׅ?䓲@_`~RR2t…B ъ,@Hv$>5CCɃǏHsg9y䞽{1#91X֩ˈa:/s}Yfgl3)$]3cDI'Juټ9ӥ۳Ё4{ 튄Z&yqj xfl~9ixU+iA0 //`umgΞ!oڵmz8]v'Mjڼ|%n੗8V>uCB-Zׯq'+jxsg0e9k4ئ6S -I_ xt&6lo2kLf$03yWk̐#>&4Kv B%Z_3PM:ew6lx Ϛ=:v9{ B1xqh~ b&j1Ra}pV%KM[IZnNF~UV9jkG }}}NTR'Yllǎ?ǟ|c޽{nzҥ.k-YMO? I@\1oX-b2-4B,YlogxbL1l;u={֛o.8gZ]@]CkG?o&`["Sи"&ܜN;c`;w4{ΜAZx1& y4>!1UGDFVL$^`$'%Kf?\=z@tРAݻw|٧-Ϝ9C"[4oCмY3&lO? .'Nl1ݨlX~Xn޼}nlh.g~1b XN)[3+ѣTh`)55 H3Va)GG'OW Hב&o\!ccnj,/ٳg~#۷m[Xطo3fݳdpx˗-'2Οcm[v4BO`ڵH/± k׮8p`= ǎm߱ʕ+8֭1mۆiv,e&b`z N: z#-MۉU~\MKwzN8W79Gc r2H5kVdT$bT~1X:~.v5TDz=2?uc1_13y$[ٲѠ@3*wZP9һBaۨvm3j-Ĺ4閝bC ƹ7eQֲ{aVD48DqάZGsВ&ΡdžX#H^()&rF52oT9HDG:c qKvH(:R*3OEIÜ]bd٬%}5vK=Y`J։R R65pP`Q'ΎПV!QZt3Z\dW [b⼉"rɹ JE]BdI;fh=kB~OdMW\P,t]N@•0vp+e]8]ٽfC Ӎ ;EI6ioETyH^% c LV3ޡd(\䗉Ae*6#1pmhx`&; HqF LEL,qœh}cB,RGŇ dff$&;88!1Yq)%TY'33=--[R0`Ł0\J8_;g3Geiú|R\RΨ8lpƄ8vv}3(CIѩzz˦TΌY|veS2]: _u%zKNJhogons]ӭx2vyΈ\7},LAeŃ};T&pX%.p@aGe3Z`tF s=|B1[)8MXQ4WgE S``+Qn1$ a; N- LKK#]?fdb>dΙUFyu;ؘaDݬ"+zb~^#b>%dJ/ IIF窱w 3ɘ7gТVVG9"wFõDPgT288kYl|(8Mi&m)8!J>glƑoGX*aX V܄)Pd(nb<;5nif : m?;+;KLy 8".p@26[-"!gs||Q0Ti2IXV ]4QX*_\{Kų0hO2Y{R>Ə+ySQTO &6TR*$:s欐ע?X&m4IQ\m[IF| IP[$]=zD[ەTܹ}ɓ'MVbmF7%/yt)xʙ0(<9F5@5 eOd,Z߰a}>zڵM0xXhQ^zcǖ/_s t%Fڻo#v킷R ^ ݺvYv-bKųʳ~ݺj/&4`fjȀT(;(\/BRroڴ -'7nذlٲ?߿.ݴq~?w8vgNYhq̙֭M8޽{#G;իd9&O DX&ڵk֮]D '֮[r￟0eʔ 6]8Wꋘfjhb=<r>xxkѣG7jѣwک_/cƎ@h}SRRF_رcö[ǿ~Mpqv%˙Ovs4I6iҴbŊF!#=v5jtC3j(+J} R  X?ܾalCy˗-O:}~f> O:_/0W3:dpzسZGǏ̧@eRծ]$gΜ[ ҟ}˪ IDATRLIq.@IӀr$زu֭[jשPDsg>sQF"G:NEXAv<"]jkΟA CҔJ6l~yBTRLI[_"\?V)V Z@!ۼ~R?ֽ3H3dG}嗄C sf:`mĈnߴR0իiSnj۠A7oΞ93g͙s+:/F푄+2$G,Rs龂ZT.Pg6} x t]w)W&7E|°aϟףGw/O#Fbihպ5eu|~ժU1g&[׮~CuS*2g^XS駟d?yQ̡ }w߸qy?RZAR ܎;:'{USSRKĹo\ިqԔ\p󩓧U].斈CVd ka~qM -Ek%utr}=SfPVNUV4tݻT(!ս=fL krSRRSRPSNկ@IPJRB?G4EZ F'f|-ɓǟ|R=D>}z2yƍ7̟w7obog=֭ˇ9rx۶l쐛 j.]~iƣD<$ӧ'LH(6 ĸ:II<͠* B٠>= 4OE,SΟ?W^ ~4nSfR_P ?U6 DN󇦳;u>WÆk٢qz4Cyy,=skV<6iS4idȑ~~~tڅ[ho .fÇxp"ܔKN)ReTQHJڴited\Q쫨7DULt&N)í۶0aUt*P= tNH) TdX(J`mժUu_ZZϿk}pA+/ ϲq`_Tl&LN2~ѕ\jyH{1mbO T/r yۡ} ~As!'CΝ'O>M(PWܳy2(SE"U(gϝ}̙+S= mLSuR+)s;c""/ed`T T-jDZm MS ӟSq?666ÇW2nTHh_!g:tG+VC<設_>Zȑh<]gؼy3[6l~< ϯ:Ob 2l={>|8ȣABя>v𛃑qQF,:٣G?޽{7HDsY. )` =)YY{4P611Y͛77oX a$U)B_ѸiS,Ñ22͛;rhN ['gOTLW8r 0悅G0c*7p?zJznPp7WGY1J!rFH؁ l;9;#pմǏǤr)==k ÌRT̋CR5P8 +UTX.Kʕ|}Հu5)k&ɸ%鉵)wtH|6i1!>1""\1<($`z{ybD]p*ggg ГZ/ `!XzzOE?7\v-!>˫z$>֤H֭u|Α%Eԏ̑.tuփXhD!! FFEEaH"%>7ju2Zj<0\r5jTzie‘f͛GFD0sUB\\][$f͓NQh, ,s %{Վ0W$@-`-Ӫ>?;W^Cŕ)~ƍ ܥ3}|qRhh(9܇|A³gυ[]pڵ*BӬY3///r*r֭f쮞gb kPpV39}[)孛77nhT$XF~fM$Μ;Siˠ4'dF BޔHxJ<F<粇$I|B<} l4Q8:CXv Ew} sv...ջn4?kF˻vwb }X۷58::0 Qn߹SNm/gjz4V@(4eE5@5`rs#, :j]C`qEy`KOMlZX HVpvBLJU*8>N"PEg.x8De$Ƒ>8cOi 8FD`,qq-7':W䔬8h9gTi"?\nB?T%Hb;q* ˜l8DyNs nzdA׀ԩSSw$k{.[.NNujݽk'0 hΜ9i? ֭[yrED`ϯi$ WCN++0b|\I¬Z iYHsOEѼ?u88GD{/mx>ı@Dro6le|j@ ါ0H~R;lKeQ,MĥKdJdĥj[t=#".K!!˖-ʕ*_=$ sSbsżxT:gX|;1*amb=2xYY٠=VXG.ξ+a"h'(ezˆ ^t3XGZ=zchT<-S  uk 4x|b,78#+,fN#g$HQ,iH @5`.MBO돨<=Цlo_?ud/w <00S>5r ė$Xn#qpt0`J~Ê+dÙRx{ڵ|WPjZ^s칱8j!+;ꥁcS\ruuPG-jD\N ?|JNnN9r8ߤr/OVH* &NZ82,($y{ T2?/o8wwnKqPjh&4[17JMa?y(RbN.)9#G0h~1C%b!ʨ^bħz"UX=kf86ϯ O3B(i a Zq41$dBjJ;pV%쨨Jd _Ūh0V@[J2BH7 e`Xg 0K5 Nȿ"V*L5 K:HɨV FΨ[Kj4id˖̈́dɓ=wr/`'Z\ǥK"Ƿۿo[?΢&c#Olܸ7^)J^z',YԻvzqb <LVZխ{ U(k֭_K27lгW6϶EmG!8<۶m gO|S P   XlAj}AYfߟ۷kxys\iUΠџgϞys6_~=5k rwn쑣G\k֬iƿ?wٲe7m_ϟ_hq=}ǂZ;o.. 0'9\ EIL0aڵdpu_~ŪU }…|tlyS+ǎի[B ( *X42gTٵs}{+ gџ~WWWΑÇWoڷ'۷owF܇wygݢ{zyazݻw5ax8#FOeo\>\%GE5! [ Wς-^Vr 73b2n`[ڵ }VjTTTSԺ'F?tYʖ-3wi޸qÕΝՉ_g=z[׸#۶o+PbжX!]t嗟1>tħϜ φ ~ br5%A?AS.ц|JQ ^Vr y'&&aرsǎ裏RR P P /{ܴ|-Z 7FhՊ?s?Mָit|}o߾ӽ{7~1d`7o&5k!FEd\\]۴yvugm ѴgϟkL:sLxx8<+\$ŋIFɦE4!qqQ-P)4hF3mXϝ Ai<*} ֓ѣD:cS Xm]g,,nݼi\׫ ʛ7o V!&O D࿖CS P P XK1nɜv4rKH (S"ki/JYhvJsz ^)aPȭ{Bk֬uW/_#e q-]fBN_mZtkAb|܏Qs4H N ?4#+?F/- M7W7麬W޻?99GU`UJ/PJrs F;עzT`l ˵>c&%ZT)gkg(:xi))pS;:ʜL^zfFF#OQ"nR-L!q)kbYWJj]tuu+Ac&O!ܗ `L%==ͯr4شbMt|V;:9!? !!UV%@0ʕ,֬piQF 4iz9*2#3rʵj:񢣣.G֨QW'Sh,lsۻ/@Nѫ=^dR P ii5q.+3kpCiOŋ H "o\oPޞ=wHلȅ 3 KS%ѷ7Œ#NrQx#čM5fĻA?OK9 EzzxoVv qcǎ 9[R(a^ jz"h@ zYDlРˡC7lpE oa¼ zmL^c, O׫S}Zaaw!w Uzy*EFEʿ $Y؈U*V,WA,ykU*ʔS޽ 4Wá>=bKAKLqKAr驩MjܩsnBg(>>ޓc>x% &'KSEE <*'Ӳ)⣁9>G!Ξ9i332SN}Yz5G(W{0s{ɩvX|X<ť /qOH€@n^Y8^<\&kPqNǀth.((hСw-Z{zϿ:, oNzz={4j FA Ə׶]^zn޲dƍ/ _5օ^եK޽W._*Ӎ6_<([.K,y~qKوAIx!˘&Lx啗,LIV!;ׯ»teQr `WtLtYMbƂ^6""#EDFU\y2tl*4*yVZmXg߷eÆ+DH,sus\ !>zk57m?|._|f#jj \Խ[qޅ /hٲHDJyZD3fk?͙;re6nܸb劯߄[n,~k]4񕝕}'6[ T=( ˞wXy˖' %Rը^䩓d%>UW;w͟ V *yc'N &K\?qѽL*DГOo PEbb(o!(Yyw5|}}0߱}(<o 8z/Fx(&ϒ4i|NǏT1W{՗B{r͛! ݻFNV>|͚5ݻwH%$C[+WԻٻ8)sfL$ŀ @3{ FgB p* a#s9~&̾vիUWޫUc-@q!~lغ8?C0g/[oÏ*Iӯð̈pSrlJJNPqЫ%+AŁ?---𓚻W( JuxYC}_hM_Q,JiF wOٳghvSg\yǎ2 Ӎ2Y Q]ֽe'~EaKb{Zok9$_zeF9j֮UJËpXrusCY>^D^|$`^8eLB2ƪzZ*&#D~gKk˙I!Q(V{z;++x;%!kbhU1s꬯o8d(yd,~3a]]/zd,D̂k,JAeO,-Gƾ. pEcd$,}   {18|_CMc4{iSqZ1cF+Hʊ Nl&{å\P^xuL;/rŽKJn&vIISN=yыdki3Cx+988c4ֶ$xw ểՈt_yrF: c52$sh,'%Vo.0Δ]T۠!!m{>{ׯo.&o+㏭Z*88{*v [ϰog͜?]\̦کm̙6mZ^{U(_^95mg 1JNJXUVU''g#AȊϜ9?699ۇsu"~j q0ݜRW_ieip2փlSՕF*oR*RYYYptƚx{ x}X.i |%~X<I',rׯ]⅋Ǝժ0.&L.|ηz띷qhmsQtҭ[.m˱QlFNxEԴ]ɒ;|p43T[ZXZAAbK]ur C)d薍!OƆz˰-\KyǗmJ3c.aw~(Ch\zAhF@R=%A9(E9[3R*;9:>m% B@ ܒ{wJ'ƌ99ECN62 `E(jiebgo* =<%/GMITC Ѓ7a=\-Z Dfb =8֊^Tv0ZɎU:NF)9KK+WF.[[;wMyh<r04C0ްrl6m fId6]:T`VNi;ޏ 9d#^ĿΧvY֝nLKjbـ?StV|_9;%U<;%Nx{(_',,c 'n~R`LP؊)ҩ_L;! vtJ#R މ%R2Ӝ~[r^:s`Q"BӤR9t5SA \$|uKUiͤ D cV$|9ؘܖ@Z B4Fe ` J,AK+ TgVMkv,s:/Lnr19nmj`wO2륗RqKEKt&92,PA!`ZȆ-R .cԒN8I|t[ʒʶ%Ј!G.(\\%ˇ<-N8 ]U n)t CK MKC#IA;% A>X,JdLIii)*Kh܌m^cN3'g0I*a>=4X)t\*p>LEgT.΃XHZG:"Ffϙ#T̤OpXKuK"N!;}--uGJR:IB?wH92)_$! s"{N:,!`4 YrꌦOH Ɩ^avᔼΎ$N2'}@z.ZH*4B@.BY[K<$NW+rTnJʉ 3%.nDk[keG$ D $jEBzN__#=gU#>ﻥTccCPpHKkKsS^DCMp ,+-vsS^R/dZ0G:NF9kYU98԰/hcmn10L]f/ǒti1udH~B Rϵr mvo?7WW |Uo}}} cqpzmmj'i,W"9qM11q퍚LlGDff3F11d1Wc/]_Gu =H 7ot}A__vXtx1._??21A#B[0$TI}9Ztcǎ=s`5SzMKZm[G"`V@ `VsXij_*(.ٳg̀+WΞ=HAzD^.UDnǚFFi-Yml%f5y=Ol2ߝG}nz7AqFsG8.966sU#tC!*22|fTCܼ~|Mϻvmټz/[ӧO|[o=u^yUeze޵k֭A_-🇿۲n|'~i'KOKt{_믿o}Μ9 ~ &7OͷBQ1Ϝ9c-23w|`J;w} /k޽|?>ٴ{~ݾc 11..O`?,vopAہ!_-[\wu=sI7m< 7߳p~3zX{ [Vwo'N2QH F5 >|K7Jm'2j+mZs:w,00t11M*[7ILLLOO0a28ق.//jv(9)?+66|AII^yFxc\[ݭ>7|P[S}~ȑΝ{QFE@eG8hG"uPkka]o>ЃyY W\geec_yՕ7r_ʫ{A]W]uUMM TvnPEEEk`٨笒L~Dž o|ݻ.Yquu9V=.;w,h>oll9(G'y_GuiimMKKG)sm+..n+5q6 kP!ϿK/!&o?O]9rt1\' ^%f-\?nw<:wl~Mɓ #GbE~gy曯Am-[u_~eNvY%1_^|9ݷ>8|={4s Ͽ`^VYΟvcڵ/{dAC2y=e%W{nn0.DapyeF ތ!/Xpݬs$M6Ύ0Eq`BNY(.Y\ihU8v_^X\/#tM&ʁ9BΔއ'فqfgk*NNNvvܢxOS^[,,c|lJ,wm$Z⡍"na̾q>zȌ3OӨQ7'sq,?Yp?mBNcF{n9}?L#f/4<|/ٵ_ͯ .ӁF(0r|YuTJbL.@JNI6/u4tJg{p8a?*z1&M BX΍Ͷ.\:n־,cnjy<\KƏjaP(eVVoEފh9sIvZoSNpAk_xoDUtіesE?7C?$W_ܴZCω,,,Կ*+tiSN4>/RXXXPXƍWT/9ӑt!@zs|x_MYYwsu!O,:)&&11Q(e q^^v18}[ZR79nDlY9W_eE1""aUe_S!B& fG__(;EӸDT?ɓ7&@>_Fc5(`|PJ B`pЃ6PKNBj`dfg!")Lr_TrĐ xu?;هI:{2 e,o=e>Io URڮkH^ DC|2 $9 ɨuKø_AL4a!@gkkkWwgwI>,YKF򂇑Jة/hfznHh9ƃhljv$"z;;;ml8D95SkA:ǠSe!0ꝏ'V.8kCK 9EJdRbhrH? L!H B Ls&e$0!@,"%B09Hϙ\Ǝ@KKHC6I ަV ̫d3TT!@F19ǟ{+BMf`s^W*eo0j(.'Nvy4NF5_t @?@UvI&#ʼnWGOOF_ٿ s^:<@VL ? 'p3 "z]mmyy)qK%~b0HB`("OFĿ~ɇ25|aơFv775^Ӫ!:&.5%VCqp=*?P{h2j0CdϙA!Prޅ]倀}ni!h҄GwwwCCGSc#vO/...8Gd)mJ-ze1TCL n{dI==&zzIω޲=9ѣF `Rtk '>Ǯ]0xGFAk0ڈ r7hSZZj̃.MII#q!s*Dc)//7''wȄO 8Ȟ?}@Ϋq豱-..f닏x(XEoЯ0x-{>-f"斖d 0b{.+::{{??dV'Vy득U]UYY~>`pp`QaQQ1uU' ZYCj(l{km9%qx`06.xr)8bP2X9IF>&66^آklWK j2 N`y.a T${CC=3f]q;:;QJe|}]]7~ѣ#F}=.|rű9VO%x1TYYތ=̙#&{Mo("/n%]FX彅%vp)3f|ط*c=GA 5Бs|8K2\kK;eNe2,XlȾ5P]̀SW{_z饬,+s _TVU<3xLMK۶m19}(9' C)--9;w IKKŪ&,C/b[!Ϝ'%aynȳ[|(u›Y@iUKy%sҒsX2?_u@xTD2;+H>&̕cvF&pS;VDK_;Wh֭qww7}2^XsL}۷`O!!>9q{ի/lm>3Fo߾O?ݶxɒ~QIo/_~Gݷ]> +z?0 &O|)A8>|XִSL)g.F ቶ{g] +pq`4'NtuuAD0!z]6E}YpP3&Oem,VGG?%ңn@OYpoΥ3u۷P_ǻgΝ;S/ra'y'brwYD;w3fe{N8ɧVIoW\9R81Xr9{e˗́*q ! 6Fř3|D|-*A0J]III }:/1qԹsgo+\|aĭ[w*o$%l# m{.+**ܴq_dƌ[n`P _[?VbG@|(<+,` k &}_lD8Bv 7]Np&P9}Z|A^$M]sd8hsWZ Uj XAA>hK3^܆}=}) gUxbZx 6ǍCm!+++2~ -"$n90֯8Ӳƾܮ]o&r&NΞݻ_x94d2y1z낛+87l<TwkO,^ W syW^Zrb:Q/l|{!zew/~-d&֩JDEEEx‰x/D[#o"E@d {cp,//C>UƭCBT9" Y &$3fUV^T 3p޶qVw<֥Mnc"&e'2B`yzx>|Py<@{i$&Bx`#$χ0X-pƪ5O7Jݖ_'+Aokq&D" Z_!$h"3f%o[GGd$@nS9cNE? RRҐ?VIaYӓ;x .H]+(͠ig=Hϙp1VɆ-l W8;Ӽ}P[wOH\O dz˘$;FH(f2>L36;]KN 8 !(ePo[$?s... pntuqpoimSc zR9Y\ AB%" [ ф~S+vi" |1 Ez8ssfhҩt 78='^3R{M$=g*Or* uI!!ՐnK2W+iyEnRlʩq:OF0TL YB%qPպ# <L<. IDATPGĞ B =gC!@ s!@5猺{H8B "@znRqB F@9[Ñ="kdnRs)$!! !9n?E&%  vwwڊmKx(* dą0$s&^q- #Gut755 loo;"9mۘD!@(#0B:;:#kԈ4B !'.e iQ.2e oD4~2kcNZz&ZM](%n[1t$*Lu]wmذB$fE6BГӤ=4=EEzTms,𗜜,G*Y*{ #GԞrC^}e}&2HTQ$0!@B@OzNT݃F{yڳg@sR˯XGF < \?))oΝwy->F_fʔ_v?:˯K,7o믿^\\" ×/[ǁ?:<^ &74̛m]m-gjjjbB -K*Bi۶mE{Ycت/ع㑇·{q/jժUQQ[6s|p}GsW54ܾ;"/uAg'|288x۶m55/9k&#A ];v}ݠxeGҥ#ܷ&N'`5i$ݿCTs3fe J>F|g\T`܄omfs3I-,-k7SKL  ` 1VBXEKF|TGMLL<Ϝ9;*1QLW_{;O:mͷ;?Ksf%5Z[z{GOQ]JD"`eiɜDL9Ő u^1X]󩮮9~ѣG_ze xb,55u QXc@L6M^ >eK)++;طӡ2[:bڧF˗/TVg,,,̖܉X?3Tp^0%{(9-9yG{b޼J=7gKK7|5WW\qY$RXXlRGR19x]8q=gRj*xc>Q"&ŋxwǎ;1 څ[H"` Xº-*++JKErsAQQW]_rR쫉&67@dhH2HXPX8cƬ&IܱTBCN:PvoMREvrv9~XHHBGchhSJ@`ms*%p刎V' WW׬Li+$Uy2-{ ӗVy9(%hx_ɠ 6)Zt&<-1tiN.p)Iv"ΟnimMNI  pGTGMzS*]rL&,)ԛEvT DpqcX=_g'zx$' #3syď6$`jqf$UB@ dS؟rm9teZZɓ';pG<!-esҼNtih#&$Az=ǒG_Ug[w-,=R,hlI% ׿\%w"+ !.AX*x jwqhoRڍP}0{s&\B@9tIt 3x巔l'_gI !OŒ @o/ u =Ŧ@ZSdȭ[r섀Xx?Z  IP̭b$(! [ʼnE_3!@"0۟AK$q! 39 M:s$ ! }떒0#&AA*# †j!x$sw\8@YaJ K Bs8ͧڮiiӚQ!@ q p!hU3e#WnNVO 6%5  0,9h֖ ls}x ۔iuu]A~pKœV2{rs`iǯqƄJ&,ڵ%a(ReL-x#|#4$Q!`=ŊʒB@g>okk啜]AAAsK PPPȣ,,Ο?'!: UUFs$ !?ۂC :;4$tv"0(յKgB@[ #V?VT,ޠ֮455500*[2gl&M?b%7(0 Tyqey&M !,֋r^MMqL=<ПOO;T{**+2e#J0%.14u`uY+D 9Zl|(X<~dzj 4D=8h %8y46xB+D@ϵs>l]_gW'Jd-M6Xr9?'Ϯwy8;@oog1"/ '7-,9??_P_-^$I#cN='/51?/̤G"5j4P]][p19AnfS􅔙 >j#9 ƍ Np9Xa=z3-rݵ" 3wؿ*|FV/YxѢE*0&OYŗ_¯С /|٧53fL뮻Tꊫx>;oiYw}~аu뭷ƺ <}v8,] E ! ˏGꫯLO!9ԗQxÚ?|׶6wy'~i{H`r\6}.im`NϡoPF5w m>)}O >0c#tmq7nGFD8t_|sÆ~4n矯WϘ>Ta{ntK{vz'o۶m5+xϞ=;w|56*epqq:u4?y򔫋sdT_d_~ժU:eV\-¨l@@^zʹ>ý{۶muX_e  u׊v{gq[PhN8KG'$.$=y$B!9QB2u/7 7iFF؎K/m8ؠzӽߨ$!!rCOlÏ`cA'OB=kzX"}W_ϟ?oĈ<|qʫ[Ā=x3W\W[cǎ^͙uO=Ѝ"QYzG|$`#G|WٽH`׮]e,Exo0hzN;1  9 Qx0#]~}ǚ2~[n^8jha/_ ƿ>da>VL:1T ݆,Ó&OƷ))U2qqq2eʮݻȧN<ǎ9}Zo mnUPd/Qr <Ç{*^PºaFP9>~DCC#< 6o|5W3v}tq,y?T_[u_駟?3a>"N ŧOeaQ۱;z?FW\uV do2cm[ٳg?#gd|n6}FHd痙d]]H}U!&{08S- sd-o/OlOcYƏ%O&{ /-]=֭{oh?裇z ˡ(6MdܹhkŋoOIIfs;n8^v3kڶu[aQemhƆy]}#0?uB<&=X_|%<\|9sjy_}uŋcEwUN3ʊҒbS]qW]sJaʗb_M4J@!`*pA/.Ǐ{>>Z6QRO&#raaጙFn Dn?V"ٴ"du!2x k2%j*77H C!08zxgz.W.P P=!Џ,>99Y4BuiR?Z4>$a02ɍq#ZKe`1qS]x"AffvccfltO,'aSC*Bi!^1%KUL!`Hϵp;j =<=ml@5LWK$AͨQp#\ts 0qj!@:lYܹ-,GΠW w)?Ff qYi2 _ZiYّ?DjpdX5^C/KKJzᢢ"eezuDgCEQ!`{ʣgh\iokbHeEEBܦ=úp;9:zSPsJKFRU,sAAȔcpiB !9lHٍ$? (A2V.T\&$""/^iDds]G:TE\zVJzzUwtt! PNHQYB 1Bq8iUӲ%yԤ.l'+7_Pu55< ؽ-A p| WeđMMGjө"B 05g]VVhz4%%WS]“L=~8dzx*uT(9^>} td}}oENUSNwvv¢W\4ZCRB C9D'yy4;ǧ8+; IIXrWMnn`(wOn^. PoNn.6`effի38h#B7CXQQYRTOuBhnnU*`1cFC!'o .Pт!! %"" Q'K!{pPpCyyyyXoQ^A)Ɉ9bD{y 7-o!8G"L!? 3{z988b\rJUggkWVRzuۄ EP9TR?*q N+| Ev1}~UG?z uuc6 3IiI(@ &G[k'#ÇGDFa ޗ;Ԥ܂PJKOllj 0  |#G!`Gظ䤳xul| Er 79aa_mm[˫*+ll`gGm 6cڇh\ԫĜ3ީ?@R\TҬFwJ3WP]S[_p"H,_Q CH"v===k;CU镞&uV3!{̙m`\+ ';[Tbc. [}+9Sa$?!06 jǎz[ʨV)ȣd KUVXx `9::9 IDATӆű#f37dJNLDa54kE UCϩuexXY1k!jp$y!08='#"lMu qEA8<马,,,lii Tvo?bD~A^ý+ |$5Ϟ}z6oeJI'd]<88($$&11mmrq/+Cr]* ͫS!FՑ#GT('b#*$4D OW..4xxNU~UU^^ Gvs+/],h<Һ .9hz{ 7fl1 ƚBݟG'!@耀ƌ:Gd-GzoѽY/_EtLȥeG +QQ|[̅edY{ry#BxumUu^\\X "#P{|ȋ.!!辨èA`.E>P%#|PH)LUǎN :$VG8V_(g\;5FD^,W䷌ rkjqp wb£Râe=UtfeetUU )XqbyD#&ڊVk{._)4P$D >>0w{3* ׇa,IA,rM<(|3cۛuH ח*G!gRO[ZZSwCɵm낏OI)N`j!W#&G$S5:HLк=!`2p$(=G (5b O3g7 Xw7Trs322*+`֟>}VRT'/77҅*^FP~P$P6pHIOo@-majm ~^qQ 6*́iiZRl\pJ)< slp9x1ޡ݊iIB@"L닥GXliX 'c*kw0 x1;w bN3J>|ZeJ!; ;} {np}) b&|Jj*~cRԵ (y_~~,pp JyDbRluŌ uڵ(-)Y GW_uU~A>ɗb_M4 PP"E9DI~O]\ݎ;& qXSJ@`i+aUUԓ^a2b5N3V،9tp0LYdNZ-DoB&ѠXgWgl[V74 ڒ%xd jmi}Y(%h#DpzN%mqׅ[DX,Ν= bLH$B@*{!S8 %t>3>ʾ )$L.#>G@z YFlrH[b &6|zvs6mO! z|` 1dfefeg >vHu45V9{]576zxzڃ?TBnjkf_ B@ vv8!>ΝK.ĝ Rb !`Ba˭it14fߔ)Yt㏃0+'Ns&<4s |̙2yn88rǚ,|y [pb/4>aDA~!d暫ՑaHCn'aa"CMl(@Q~*USRRzfLĹY_0I{y51oCQX^yJ&8 +[dZ!@H!-!1RV"9?Khcm Ԥ'Nb dR(*,.7B SA@@|#r sr~ookގC=*++B T o]@Fď`*t!p6Gcc#T5mp^M\\UFl8=g'g+54\xl8bYY^/K>SrmuCJY"P__ĝ!GT466@>5 &gu| +!dM{mee oL#sx"e<HO?sTp E'!O x&YX0q77(/+AYň,1i\Օ#G^jiht e#X< OM>QڷEyr&LK"7UYE܁,8D!YYuAxjM%.\t:#O9ϡ“2Ag!31 ++"`,1e5,۱q PEKv2\5gNv<ӵ߅0 Pr'سg+Wo78s\pBBw =Þkl2ZK1ԈEA3`N cS|RΟ?/ɚhoB:Ƶ>Qjjl >@{ifPU&B$2@%;wu;G}*UY:] ,HexQs@LS}XY>5#aDYa 2ïRxUSYgs榮>y.y"S3VU $GL+;Td)CaX˕iv%Gcv@^큷W]{Y7G[ 洡Gӕ~?cP,9fBr%J/X{X}1v-u;va]ERݷ/b^cfn}w=kdZs禧Y3/eb{"b.@a C3'\{=nw25MZ znbj4\ YggGWQ3ML0fgfξ{=̚orX' ;V]>\¥h7<>=2j/h8G? Zw:5+JmLAuol!t07h@ ]p#枚og[+spGM-ehgxխBH B  pa؝edʄ j8 3'BN18j,.z:u)b[سdv.  !@+$f2nL5W$E(<Hn i<کxGCKnSlޤ!B2cω#g4ZU]s-(RXu.\7I(F$]ӛL",&ƈx$RF Qp+@:>8Q&By Ԥ&0B@&Cy +=6?П}}oǎ5; B ȄJe B"S!%wA>gwȑo`(7 %]q27ee/\H}$bB  9=CHKJHo>J2} )Β#$o]6n8 (!@@ȜCp<Qpp +noo14"BS2̐#&8 eLՁs _.O& B 2@q)pSs!mА=):E_RZўpTYs<!qbp`_2ǐ ?gnشzI'G!GЌͣXCLfX҈*KSPuaCx=B ҊX"ѨpKk+۰r@Ţ9m8eBHˍā'"]: EIWCzNpHBB w ϲda5GTH3yq7i'YDU\ȶ}-@/"ǵ=ǀzMd陙;1B:ߐ d;m!#uP_Ngr2\\ȴp]ZWU~Mpe2y4\zB4!@L0Zm3j X,,Y.38Oݻ<JollRXSfrj Նj+;;;'''׏^bb(Û޹s'H::;qRm'9yjr?klj1Ԕ鬯oe~LxDӡr ,(GjjY"=籘PPi^qVW,}Kkkv| `Ge B 1Ќ544Ƥ8zT,شssSS;!mvs9,vocVՕCz{zl+?LMvݹsfz,1U!2JMN^j3K)RJ9χH_{GesA1_Ze}=L)O[ /u$ջr MX+'&& `)ʅE"^12&ml\\\`hFceE%>ŅElK1ߘU%qn܄{`ê![&F& 󭭭,mimYlR x>d>h07re2Cz ˒uÁp\nB1Ic!qZw`Gtmjj"YHd36fjhh H(< lfcTfc#; B/{WXpd^Ι3Ϝ9[oa16&ol oLAܤ5wX Qdhr/A6͗hv$ϊGo2YVSRD,++P!lFфV dA)ۊ I|CԆԧQ*/6y3`)ժ$RʼdF!5%]_w*޵xqNYA zlimc,>vMMibɉ1ϹQ3e4_-n<3'L& {>ZU*@3d o%ؓޏQODݩONg,ˊeѮa-il~ tzl 0-y1c3I޸Q,~7FFF[Z*CuQ]mqom ]Țe2dH9֝XXY[W=q+з,/Hln[l ʘϱG=11֊Ǒ c 3ep.^񹹹ݻPceXUi3DK[rt# oNE)4]Vb]rB`ICT_]]2&MM{X`H657i0_%M55Mf 2Ë@QE*l CAݢ~߆ǃ~~LaЀ(|=7kvYyi`c>Q0k-##u=j E߈xhK"Fcވ?|aa[|ݱeF{MЩxDƜaؖPV]4Uջs62[4Y4h1 6A ~CR#;&0Mx0BK\n"\t F\PHo8TsRU>_q TQUQ4{1/,cܙWՕ듈XNm2Tڭ{87Abr&b]KUA߶m~_7A^bO3Yb1q*?UcM0 y@* Z4<Kcc_Xi .Ë$yK=uǞ$ ##KK֍ jOOozJKKA6^< s5JzbeW髱<갽?'ehn2)`Xv{ɆwKLttV'md_utc&+",;]jueI0 fA?L , f-ThLMjkio 9T#{NEFl巶rX5:u@l4}}}JeI11}}C ;~/WPmy 4wCcjDQvG \`5Ƥ։%":s766/^olj91nSkkecW{4y/O<;B滰<<0 6' v*tŽ6B*K!c&Δ(Xgg9vMMM8"1v;`3B r<6TurlsՀ-iCu!ͭɉIz8gʕ9JpR83lB+b##z."8! :sl}}9Y[[3|dɀ0 L"a̞z/vRrpg̃B/؍lx{GG JRVz|rG Nd'=29ų^C!O % aR?4Ec@^*;FGGu:uخ.fTZKJʘ5=[ %E@p_YtHjXOba"c4!t`z`9b[3TKJeml",ՈcP7w-0{gyH~$*")*(,zT! Fx ,J nl1rl? ,` UEpF.us!a-B& `zLBKűnC#k{4ռE PHY1oNj/`F!nyYnr(i]CȞ۟!0DvA@xa`,Ol~=y!Ƽmϼ(=~yuәpT CCH(+dh H %}K0 ̞#ḁ)FpE (% :ĉŵDGdӲ0L:LO jO8!B1(p#N5e+d0fhP3"aRυ-)ٕcNe nݒ1CQ0=c~&6,[$&p[0# 0 phݒ^!i%Ir!F !߁ aQH=GcC!@rmDB !琿qHYy\P]@"\Q##1Wg RF~ĥT*Xezmm5-J @rjks̓17q!"J*)];Ά u!РFŴA=G˜IAmf#?=!j 猆/_~;;=V P,/=}F #?:؊0sx-+Sjug܈wB  BYR\z9 ; PHR=Vy\OCC ca/Nx[ *UB!@dsZ!B粃;J!Rٟ gJ>`>zŢ"H*xb@>$- #\l{+5 N%.rmn ~Haޢ0KQZx|>ϛb,+I^uȴ쿱4X޾zQ->R/m,MY͒0r)I(R~ jhhRk0E"(< ''M(cqOqF+H{bZYOLu-کC-`"-i.CXua[,3iU=8} Fb;p]l Ě':}9)`KF\R Xkk:,$@#aW^oEɞ=uGKKyl]}h3(/+<O'֤>z`F l6w@kT .]/ c Hي0700kvVT lll*M J?"6T*^tKqcB"۷LR8:,gk.Pܐ0ZdHhjv۪p8nܸr[.}t 5շ.?TW<\f|O _r۷o 7aњTڹCNƘ00FBz.EܖگO+?Y|UgY\ֻZ^^o~g~>{|[8XUUKgznŹ yؘDQ^\_7'DN%hO#F]ݎ|'…-lq:FLƘ⡤(n!{.ݾvԩݻw5N~#^m\uUߏ8dgo,:|XqK=٧z~ò8_o|s.:Lx(G@xU|x/}s~яt= Cdb2򅆄$f,ezs(bwabno-^mf*oqoc ĝwtT\a &6G=V1+eS_W=$KsSmDY {w]w77G548K/*gO~_5pS~}|W0xG}ɓ?!=t󎀄$^ӍzfDEE7Wjpf=~qZBZQ^~[Q^deWG~؂IzzRܴ-//}oJvL_ϊ榖o 8{zUs߾ҋw;VVVVWW{W}c-2z>;_W:I%#8I0JB0†ܬF)..괰BET>xMLLsZ1b^jzf̷hVߤ߿oߥw>U}URtpS%bm;*.^_Gn̶}sw-{G &fmvll?AɄ22xc{s'|2n2V6(e)}Ff HɞX9Bv6l-Yzy]Nײuy豻I? KfѲ'YV_}۶lh_wEnSO_>+Z,{衵?AV+@»bݲ==pL[gS81_Xx/_Ѯ``ph5^\(]ǙO¸mRa.^}"~aj %11??{UWS׏O_>_U:U~u;jwpպ6896 x'|ⳃۛmnO?j}?:.@W}Ϗ~;0vq[yyEOO׿5Vs>gܹ'4p}駟~7J{G|)N!*&ܦ}wq{ #[(kXuYB1rA.qYRO؂̍ /tθM-t>5~vw̬?D]w^Yt|PZl~Cw^ݱGe(_<7_'Ο:2?N>{<ʫO[yt%1SmeFlx8GbSS?nXh(_Qæh~{,Ed-@+<{VWWť٥+WF?e"CKZ*y%)[zt`{iRU]З|N"$!qVH#aH\.q/CCC+T^3/J |﹡;|%JHpDVCMo穒*gF|ƘoJ%#59WGǫ*XT\^DNJ&L4\])x86;xF^ /T*L&qϻ~HZR={N+љ%R"+ܛVvHH c_6?kH67k.Lq# g>]rE7kk ȺfmO=g;3ntPr8*DZJ)˩N˜SÑVf(d(8JϜ>PWϯ|GF`i:":YlrI{ls/GΪ%,~ *4ܖ aS!PmQ$f;`#.ɺ%-P,űtNRF94@vp90],^p)m?*R7v+;keȾ dfI3s[٢0={T2yZ]^gBPerHTKחK;6V+JeeZ\^c.P a&scHz.y%&vx N"5ꕵZ߷}Ib˕ZM> c`mQ(.JbTT,7>|/F$)CWSFߏtH!a۾ S4 4! )L˜hTHz.$d B Ujh B 0B@j+RsO5nI\ )u%7HMR+=&.E 䇒Cl1y0빊-sup|F 5=Z|Ɖx'Ҏ@P|nvm䘺/m5O@Fڟ6BruXOϴ?hT+s4MP*R1Ш!VnIPh!WHNMR+%T!PX(H kB @*Nvg7RN˜& ha&wR YpP`:Lyi5uK"S%Nq1>!@EQoRdVbl[ ISa<5IENDB`kvpm-0.9.10/docbook/mkfs_main.png0000644000175000017500000017235312770324126017113 0ustar benscottbenscottPNG  IHDRe{}sBIT|d pHYs+tEXtDescriptionWindow Class: kvpmtEXtTitleWrite Filesystem ? kvpm ^ IDATxu?nH\JTBQ0"& 3]`S@ ,S)鐺 ԭg~9p<3{kΞ9Z|X-a'9b9EZs)N`KqQ ]{7>*U4EbbeZT/X\ $lib:ɯNA!U2NzVq?C'9SrXV {_`@=+Γ(8EљL%Υ"A{}Q9f,:$3Qikkfn/|`K8-j&UŠ7R@HIk$BZZN mR3Ê?.bKI#QbE*TbT i^SNSNX,6A(*:Ƒ#GR2<Pq=J&G )BYC݈(i >i@ X#^Fgh_LA$2%Չ?LD[[ t ZB кHP`Bst.uwBT!Ӡz|0"]&$IC%>nK(Hυ7-4ɓ}~mUUEEQJ1;S9|"J(0[kIq85vQSNS ԱT|n~?gyPt ʨf(,:Juٶciiԯ[ժq Xn'NТEK@ ;XZx=^mJjYJvԟى=:ҸR?%J+GW fcgi=2xĕcL,ȷ}h+uƊ^@h,33TUJMԓwUTɘ&9F$&!;[*p]qdtgaѥ8p+*Yf1{Ԩ.ŻnӼys[^:7oMh˜klB5ЖXx֭mȪVS{uiBNhܴ m6P\Z={(s:).)alؼK4OAa6Ftqxu診%PK11H$D{_Jm.$:I!@n% 6+aTU=::nUhk5=N"0E%PWYcDazP" D]'bh L&*Ima/#AN$f/*MFz$fU-O =#^yUe1[]ȌyAYA0?_;C[o1y橧xq#&N{`gddb뒲y8*fB@wq 墴£GٷGPt5r Wy5kPtH2g1Q,LS&I$U3R_u,^+@DRu#z,٬UOyXe%tNo_KI^D^ FgAbo *+VEĵk!XTć;7|coYQ>ZDqũP|0[P!CPA5)U{%eؖi٫~<[غwf{Fsۍ/Oux?5ךR'*uD}Ur%)%|t:zJf)hj-##IX1U&ŗ^M4x0[l5(*li~"W<$Y^ƑÇ|@-ȑC\cߟA5C\#bj<`E3'|7_#>*>nKGqG{{CETtuj׎4J)DВ@O8\N oۺU4nԔTO@iρ U.]zMVEU#D_h@Z101^A!=둷l'B[WR`9ʊ5xr.?B(]]ZӶpgP[YcMjԊ6dIp1ҝ-[Ү]$ё \8帜帎bP[T.Wкy3tW ?nmiBZ\zO~Oѣlq-^ݯ=%:[hM Ҽza7_!xlA;mUXd?+)Y@:5}⻽Hż5rICf)<ҷ wחr2*YİS Z M:Һe .pG?7tM˖\6RᝮWwVFOִm=-y8q6[ ݪW_Ebԕz\&dV,RixWܥ"f>}JN:%7nO=$eeA,_~m)8K/ᕗ_'d0ct%8}Z={5Sz5=K.{ewSoWMuzCOO>eSYh~[Ϧm{9TP8NթAJS&M6OK qx=O>TXߏ!jϤ3Ǒ =*S#Y5Ai.U=d I28U1~O*=Uή|/e8<̪]ڪ"XlXwj[]{g%[pwb/4ݮoy/OY}+ _Ϝ>F-hݢZUw9E\?|ǘq_'[uyk ͻva3ɝˡVsWՙ;Q0#~odoa6Ӟu,v{Nǘ3-oU۷z5?ܡ]5W.+7&,y|p߬˛."Ø[}hO1_1z& &9Taqecǘa%gko}k\*;n!eXX{W{sS>S@b {a.XΎIøY 8 b4Zq'Wvgn="_1A_G| 3ch/HDŊ|핑njo8mXخxc !oF7G ?IJb.gF0O]7alw с]jӌdhΆƂ*'|&i'ZGéx'2x՘=v,{'4r%9jNFW_{ʫckK@b/e)c}wɄ??`Y2 אa>q8)W_{-۶mgϞTrEEGMhV2$%Nj8}B~7|'>Ǐs9voNҒ2.Υ:q)cQ vz}^Ť/ٷg^#P^h*[&łdƙ N&fBHh&ŗTXOjS<Xz:lTƖmZh7i25)kµ]7l9dg+@F[}78\@p{ |=xK(eY&Eg3:q#,EZ @N|kQ(@|n ],>-u 4˸`6t;{ t㓑GGiN$׌Jbn2_xmЩ\?)2>|%>n7ysYL\#@F;F Z)\ڏ J4xK<֗ 'wv=3IA@⻮{'5:;rd20|HgX:粧| 5q"z >KfYxU;j~*W@([I+ ߏ  SцC.!+B!~{oez{yx"C럢7+mO*BY.hȰ.1?e_j_4dػQrPBH%\IrGu~)/B͋o7 0g }7XNNy[IE##o"dGE鐑N,zDPtKMt/L7Avk.pHlw4fF&1拯fmrT2NBii|@nȡ<ƾ\r1wnqLf͚vɓv ч1}?'7_qǝwa{xLirOfrt*Zfԯٖ*qfEO),*ѣ8,9niQB&>==;+Xd#ӱ;:]Jûʥ> 2d`"+Paka!u8w@z⢤zR"!IVRSo Z?*%85 yۄRqy[;)}2}v)EM^dX.&Q,? Bz FΟƐ:PW& x[CN$/)q Ui>pL}=UnEx1c ˗ m\4fXâ Y06&|(_ڢx(;%Pņ"ZZl8,#[ V!JKA"h/k# x( .G3~*ocPL`Y!|8ʃ~5H"7$B_jBtY,vJ0PσY1(-2`~_@JBhw?i&!QnM%/6!X~/j6삏# E&}>.OnĶlaikFP'=*VzXKmh2'h%H9t P!C7nჹHqPPOZ4f| y=|W V&}1NQ|0za{  4lc?DuRe6r*+(>̑2u'\̄HcGKTM%dA#S:)Tua˰>4FFuX{" [nF&\(T*N3$M0 8k3J"HW$%$-NP{!><ߊ5lp5G@26,[K6<܍=r@wBY݀wmr-DT95T7'r#XY*͓nzeKгWAldyMáG0џZ֐]F>|55A4yJD}_j#o5AНAϫ ZهX@FQirHc3P!@F>r5%b2 z0XN%7{\>r %6GM4 ސ r˵6NV\>j-%Srt''Mmn V>Y;qOy}Ը5fmZOoB͵_XP w\^iLÄ jJJ:J49*c՘}Q劘m6b|l's>ϜE?vTl 5wݵCٵci!ϫ6v5HOa{]|ԯW_V*WcʞJRRHjKg*7nAZSIOM#==#;q ٤mȪZtoHjzw=dGZz@F deUfʛL9̉ʀGu~ṴLQY=Ay:eBO'*n@,TjՕZԦg@.<=:AI[p?L*~=L`|8 N 2/Z[fwpqthNM3hE3sQf 9MCwi` ͣ6gޒA3j1Y}=|헵MvdH @QupG~d_?C?3toFtBdd7m\סW!ۦx^=n|l.!~%}E|k4JAOXx;h23~ۻ˜{X[^v e\ߩ0UkPy&l( >^2r¾[34Av#[אm{(Vt̵?z' erum+܍x/O&'R ?H(c]uB Ͳd{FVVYq"f?# #1; ͦf%+*i('b" U@&YYUt@瘝fFR.+IVUuvfU*i¾pmܿCCxM"34%XʄcGU tL> /GͨՀ+RUhĩbvϑcԂ\HfFF@<fdG IDATEsn>j#%%{J lЦmί]-Me &lաOR//G/cU,&t~]OBD/K0[-F)xJJk*SSG0-BJ(͂--|Ja'%-?^7 #;i)x 4'~t>3oHMa `ucB׍7H O>s{gDVÁ.xACHذ;R,Ar )v|pЍ^ kG$_7 ,vNj_bOM,d zХ_ѧrAş__5``ٱ,g׃>: ߌ鐰J&@NotM&Fd1 -4U͑~>ƍHOyf1{-^ڶmK~?GaӦ06زe @klڸ4hݦ*f;rM6A lM6}~l*;e/2I[D}OlNͻ2Q5:ؚ[tusԯ/nˡÇٽkN^dggvIMK}Rj7i!R'g$,H,ZxrNo3Sb&FТ*FD̈Lj~^D~݁i~lߛ)ۄVlwMIR &M4M1b : 6]ei}il݃fѲA]͐ƄNL$fPאa)1u]u򤫧ɷX0lhBdLz9tvԫW}}U5/Gc'C؇'NP_Y">`okxBػwԉԫ_Okjժ4n҄ HsL껄ǜv;iiidffp8BA.Rq@qY69 &'{f) #I"I &r"q23b,Ăgv'$&EP>UO)/$c ?IlD1tyۃ333q*띹qcfawHW}QN!!75K.r0w̤,s }Es;v gIRHҢxgIR3NF($G 0Y_ŢlD2fhb%ZR0bMD¤xL Ob]ló"8*^K pd M/B,ұ( v¢Rtd1HNcQ@Ѣ+TEԂQ zdM+RqcVqe ɺ%E=@fc@YXH0ԳDĂfV,wtkc Y'db]ξ?;bUD\ }ƅc$#)Mߢp R b-ݨ.ͼy ]DC×E\jui;umD3_*dꌊ`s~c/N;]XlL FS#}șqcI̲M.OtoE!f Ӫ1xC0IU?gL,;(@f"F'24GdZ*D^-gҰQj4xZtzP*t>D DH}Ɠvh P@5DF)EEW>, `zt?RmGg#V XP˜H[9moV!؅3&NI!b]D cZ]g%`/5ɘL|RPi]Y:I ӱ#Ӗ=RqV&/ цP>IT$AMn,B @1cXMN҂&RF5ɚc¦;Ƥ-3U0bPۛsKby aKՑfƜ~T;LEbaM}c1C L$b*Ep+u 5M},x޳<[r'ivFÑڛ SJ AS$8 ܩ:4&ˊ|9Ljs6vڎ:(. 0&RM2K4 ȒA$bk 'Q, 0:}H2$ۚ*8气i+5 EUߐS,^Q%ї/X~6 i*('(-Z[Db6A3)A*/ǔ⥇!LpA9=¤0 XIѩJj;}=D̠lS8 J8qdL"9RK!gQ{Cx)A([U_LEI4Kըn &MP%jҲɚ"Wvygl0QM"~ x Ab4'  TL;&%1Un 4L0X|<Ys^P?!c" H${bNJOТZvBB_A'ia;:N#HL/@iP*>qS$7D=44pN$gS-v1BXG qci'cxhewLm0p0EڞD,v mDXIYXD=ɻg}9 Z(Y%_qFHZ0+G! yh&Sj=Ti٩$2r{,*1pa;53~RðsDbf*I>i+M@'&Mb'a&jK+A2w_%4-{&bK7A j}?Ě2FMLAUL4U0cC,BYbQ4}E6zdj&m"\b&\RHJ0r!Nsl%Ј)eXvl 5fe֫IXlqDLV"<&KгNŒc$bx1=#9dx"+4Tɼ5` SI6j4iˉfZI )(gʬH&$O U=4Cd,R`3_8P8h~ 8M\u|g@لAͿf@}:@d'#[ZC4tMz!37]O&@ꒀA2- M*JN'3v X1IS0j~RWYtIE0L5}S-E^o*Aaڌǟ4s hóȘS 5$%}NƝ|2%Qrfl*KQ6ILCFW!Y$̔V2X/DLE2$I,:5&Tp445054 JQ\j,dMUȊ='M44$Gآfq Ό,se׸#t,S\"ko%B t$Lb7K?$ebQ4i95ȕ9^db;I ii$k[5>鐻ȚB+Hآ&#JqK] =R+!Y$>; y|!|4)6ALgkȎ|);&H%L_L,D8"B.G ?IvƀkkA$*r)T]%&5efI[$qSuRW ^@Igԝ5\\̓@jA ]8o|ؙUJ(@ʓel7;7)S1A< "cI$jJK5bG0z ՏE%m" 7u,Mb$qb"s ku.ٟ+gx2,%M>qeHL[kc$ drAsIB,؟$-t*(ScKmIDd)V1E(Pr\uwF-jp#ZZ$.O2"zΖ c%3VITI5]msjq3k'hCMnw1IK£iH40&jv40Vqe݂[.%ڄMj mC!Dv-XțoE̕GKNhsl)q6%$h0TUHt0'Q3cHQI!ac-$N0$MD!Vr( S db#LWn3F-S&S19+(^bgeF mbRp[%J ss8I%ŏlBAQ%]IDx:*)i20`%k68H"Y2ۀEA&3pvXɔ(.x6^jnȜgxVfKgL$lX؁v&J={ѫg/zy~Igxnً^eDYe+xg/z|eI 9!|z|xa !zMO 3r0AP?O[WY^+ˀ"[Ôw7_CճW|7O=G\b*#@Gj.0 (d#y>NlHqY$:x'o(ڴuS͔'>V*"j !#<3>l%&!|č-45~3䩰wɸ˒!/OO%G@ZEx@(%ҀZn2c=4ľn2փZZcBHe[簦 CT8~E8Djeߨ̻pzɚ(}$ \peq%GhF$ Dǹ03zk^ ^[i}VYomN1j xh3ͶeKXW5z^7c#jm8ͦkquzw]; sql;sL=EN('̐79W= r˥ *dCAgϪCGz(44'f q\uSP&i3p2){3~{lr?;#^aR琫ظ&R{-?)3`,7u.JH:P޵CN4O܇Y Ty2m⨯%kw{i߬269vPjpa+fhu5j'` Kf^<|He+xDך3b֊ԻJ{ 9{0٬9PBQ}^zw<n7vA#nZ);Ç1#FzSK=(LJ}'_0sVϹ77^9~-e+#n܏wU{-/MQ8˟|+}YۘX}ǝ@ZЕӽ)wa>+-w`O.z/a|me$ ޢ::9ѣh8qn|~ :wwR8w27W=>۩bq|r/w c8r앱N 9*Lgc55Er㋿`8O9 w#SW>YR_Lcx ~_0kv }٩εĬQ$eږgEf$(u?ІGb[i[}65a<ム(O/)N~Q>zٶ0BEj䴡FN^[Ҵl[Y{_>Ι)M= c]3MHzQދ3ޡ3+xQl)j3v Qm7 ҥV]_S н^ 6\1ndS\ޫ*pl9Wry-+8oQ|&aHLn̝f37sX?- Ɵ4Ql[v A>xjjE2uZ[Rq7~klKze쟷U[Syx]?#E )Œ$E{v;ZOlakulNBz]ϗXsd-;OJ,+[|Ԓji־/f.)&IG?dE(w}4+f=cX%z;k3^^_걋O p`f{u<ç5'b:xv6 W׮C.d?_Ċ~ 4Goy7l ñ i y~k,^]*i:_>3}[|W^ɘ_ڎɯn`9rԵ|`_1"R y<7\.Cc d؝Q@ W| l:8-֢/'aellX %@ez%"d"բٜڸC4dd›;%Y |G?4$Aj]:]zwIpaڷ:41B.kk8 wnp?37Η̹,X Uv2/i/-`XW(eXQlx˟=o.s~LpE"nx:0fÄgub 4B42帥͖s=&Ĝ?36w[R%su;P}0r̚?'>ˀ~8~ջbMeIAKy[lpE B ?zv |= 3os_, |̎ٙwff.f1%F;:wY.[Ӿd}],bMc~bE,3'SwͲpXlT|;=*3s;ZL*@J{^^ goE,3o&wͲх1^գ-/ZҥKYt ]2@ aH}7`W+%#x~4/ΤF@p4&ߎTآuE^ _f͇ yn[蟸@ 06Ҟ~7pW )٩q@.q@`,EYO[`kÍW. {ʐ_I`͕SxI9.Y嫸_MlQˠX=Hs Z^^;vjt@` V7%E1cKhεk`qѾ'.ϟp__CQb"V-d*Oc}I{ѱųui[Ӿ='9xIl]^gƆ {yϚϢŋX:g66I?0{l~4Rj VEbkx#'F6podEq(*O Vj\ڏf@`,V*fn ͸c2rg='>ş`mBg^('9t6(HDDoMm9).a2s՟$H~ÒA]]uWt Ԓ**6cx.:k]ژLEOWV4~m`׸Gݫ8K5uME^Z^V Wp݉V aK="B+M7v:eY/ڗOUZNwyr/+E_e})#Z!! JU7>q֗F568?8MwZ DLJa =b\WF2HDYSs1ut= /JhHjr#0F_9@4*Ы^ز:ru++;`cGM9r6;k588&HY0\^{.??2<6EO,ά+GUɠ6)v Y*cU]SPkorBҨ򏨮TՍ|tv9nf:T뀋L~!&'Q= cG!r_[ L /S~Fڍ- 7*m =W _f촾Ul y5i>B ْT)פyhDbMZrmf; bmH.u^rBkouUKvJð卅_kWauoBJi(r)ܹ' D涪Ug/bnF;PFqL&1K[bu\P ͇7PwQm~Jr!$*7PA)* 6P HQD)"HT!.%$w$egwfߙywfvY*XyUhjMC.{ɗ5WЪBӢwŖmo7uh } 6wr_ĕc% )|piZZ?BhL]`AeC"ޣj y7Ǐ9ݙL["bĊjWٟ@ymHƗ~@\4gbRhdWb 6PP2) %_;MH5&n-ٹ*r'4]RWmhcթ4 W "t__[,gRTP ΖE{T %{ε4[s&65ּ:F (mf TvoDH (]sUVR $(~x 7=L=%)F{D푢^w]i"5bٱ`$mfގ_^郄F\fiEdKIF7^8Dn'إ͙8~-ѩ*4O]W03w&AH~,\﷜~_YRڭ><-n{,VzQ625v|X a+"AXޫjGC )+2R \`ȶT H~WH9ݿqV/ן$^:Hi4̗d4F1km[Jv6}|g:ٚBܹo귢)SP]~Yʮ1[Ğwۚ [@~@{b纽$e&2}ϻ""WO`73or0?w%[XrF6WO`ʅ,&)tǿΧo?k5UNth] ě$6@=ҝPd~t!c}nA?OL?j7u!/)I ؏zUCӴ<|1~9bŒ˕L;I)s z._C%6%<ْB칝,ykmnN:ұIʮ|_v@pKV3d7TmtmR,&b_Fν+ѱ]ydrMnI/:/Oiv{N~]'{ɴs;+h)c ]m'1{ٌ7߻ ښ lYnk]Вx4O;q~\qd+VV^si6Ŭ3%+YҴ_:m";܂\- 5weyw4dc1|K ,X.|:_<:jg.aߗ7[6L#) UcУu>`?$ٲ2>5i{UnStO[:MKÿ(@jW(\H? F=G#o5n ^J45F>ș5&ۤG5#ГZ Rߦf ho ?~L3 LtcϬSc)L9<-`]c]vݬvKW^z_TYϧVE9 CEN!o6.=G,\c}Yik tc&7v,,)v=ڲ@C 5x"B,CcƌiNCrG]1_ح+O4=OG٧!`t<YO9*φNχ#@-z#1f}WBgE/lŝ-_)YR^$ 5ikmCe/1)4=\޴%f4l@Ci91d(xWgr2WpGIFe) yuuM[ɉwa=V=<GάelCJVM.s~0,XϮ1QM |W@`.TwKP.iٿ3TS}C̘vg̕?Vv_Cy٣0^ z -!P4mjhZ76*@I6%hɾTp %|T |)YF]h+ׇKXd0QP/IrhACuZnWҜZi}P:m_/mh{^Z>p)4˫z % Ȕ>-O? ŅSmڥ!KxūJ~yFFypҫ{`il@ߦ :;]z? 5rzצ 1q@K"|dUqnaщGt`Xd`Fyc5 1>8MwϑZQjNO3cZ9.),KF<䎼T{(ZR<})KM'7'uγF\L`h>~UҶB-XEvǯU+a"Z3Q[G6rt &eaUMF@1r*H2v%N$x0P &t?%"g8;ӾGA1rfktT+>wFJLWoDKM:p .#J!!<\;a@1})SpMCݩp\^/v"8Ne w2)ug()<ώUtwwT@p/ 20qɩĔ=2)"2NCLYT@ (HtzB@p#,I xG@ A^HTQV@ AcX)@ (L /eYjRPz/Z-@ rRSS:ʖOA$1&'sVG ;]^[,,B0M$f; 7U#s1$ɂN#F eČI~Q>RGZ1ML$lBHD+ϿNPdɌ01*[p5",\p#fLO2۷ !.uܡsmj##{7ZM PZ5N8eDXع@~ԓeф(Ȳ"8*(&dYFfފ&1!UElmcDXع@~ԓ/EN '#7 ;$Qja@ع@`#{jEE2EVP td0QZȢh%ˈ0 \ ~=t%w EA}zjS&22L{ʰXJ5P:aITzn2G^ӈ7{5in{/ݕ,tEW*,[3F;W}sG/pr3G+ikօ$i=F6̿Ue*)r.vow,PB$/-SoLBʛ׆j;)Zν'Y";\X=wf9\џku|?{f@!Lm6,[zr $tdů/,7tt\-DZ_VALV%H̉}Y9xqK.E(rw\Gsu,\ݹB9܏V;ia߉Ŵ[k>J(Q?teۗdmT\ë҆q-yaxȗ@$~wKn\>Ӛ3>XxN\ƌA9\JR. Ge\o?-2؄Q׮sdG SˏЦL9"ڴ "Xj)sKpS^ :~}2lM_M{=Myc<4h^ݘח4;@vA9s{LgټCNٴkMڢR{ڕar1-bi)\#6<u6Ep]?MxIH(:i5L;'.`}rH) FcΦ^j,&_ټi%ǚNYD m_I{̡u6Mu%E&^kLrSXvY:*qrLa`:5ϒ`7,}JOQ姏P_ GxzK(e)j-&BSSi~[;ϑBd1 3sm_j.'KTyϡ<ޭ:1^}&f]0Z=iOl{"Uf#q{ԚL4SgudC]gd6{NcMxZ[94E' ,zUf 0=+z;\;؃Qt[rJeX7a5Abw uJX.cAG`zt?8öիz4FՏ^M`p`Wӗ<Ɛ 5(]6ȚalVw~_4k}ͮ ge1 #*\k ֔8 (h-6d5?שZ꽙4OE}})e>Ϟ85ӬBXmQ;]pt.j^IXo?0 sd =tsy{i,N3d|!cSY饡*WxŒm_YƧxtٚ@EE2* L.q_]IuFx<3'lǂv (HpG ~ٽ}#k( qp$S3GS>㛏0O:Rme[[ 7'F-N^KSI˃4o5z7#PYu:HA>-eC 6j:%J3dfsB5;)W㬁f=Kw:lF>ZTUowr=Czfw|:{.^{h_Ŀ⫴9 " KGo`Ũ~Hj&;gWG;9*{#ctvĿGRρsH.y:M æ?`;jCUSD WOj!j~d+'i!ȕb{?2kNv?/sK!Fc[%Qp٬KQ;_0buo԰#$(gSj.}ZurO(].-C]U. e"3D[  !]I:ʵJȩݜ7[}\d69m):Wyf{|F~uWdŧ!N5(icN9)k8W~>Ԝ%}|(Y~FnȍmjEI՛0S]kCNn]7nōCԢzM $ oqИO nʳ"?,C.ͽ\&PeNq1ml5Wu$޵eq>ܰ#-rV[_rB{9,T /7DȽ~rdz#e-5slډٯa]%5KxhêPv}wI s|ͮM>ӛJ9FO,J:oNk-f,vNɡ*6RdP7).$Ieܓ) p?8z"O_Or.<#4+sT1]aϚ|8WVhj,{Sd]'[>a s+[p|0mɾWG(U/G!TLQC|k2134hg?7b47ڔ̡-nx^A^&?҈!RuQg_KɺS$n.$TIr{v(ݦPn,ݞZpӎ΢?(gB/^|q-U1=~r'm$0ehpE2P"6OpGsdi+tyx&%g8yv9a;'jA֖M>/eDE~,aH5W9FOԧ"9.s)3gqkRQzkbI%(({ JL&SMTj4|O?.O;j蒤!KWs;|p?^N͈uh0c64HH#_OBAk5c4H; OkKij ) 8l}34ԃM3SkZݑ o"ʴ r{24HV)I^}Sh2z؟'T>9ei}K,5[_>?Ays24!VfpWv..Dwd( żhߍ8_A^˰Ym}q#2aXW$z3dc&rHxmS[14591l `(TL_?r":'s#()N}iG~gNu*3ͣ+Iܼ~ݓG܁0~ wl ]<6 =eOM-Gd~>AtKMȇq8Qă=q~<ș Z0:uܒ 4*f@X2h!hT#GBek;r^wz*wϫ2;<9zB*k^R[Crd0`շ #9W0~ZРAXjॕΛ{7R-4dH>;fUbsrxƥ yLSm:˕L+YՖ!C)qm/!CruxPN & )bv=]_)A^oK\ƔvuOib33 K˟2UЪ@{i'r)yYml/s_;-3a.EQ/iMxYd}k2iɗ^% ĨXKo廙y^X~]{S##QP|2Z$ /K]5uK{PJg.pvvNjxU70=tNٓROeyq]at4?flnt} f007P!sGX̙M0X щ)X\; 3v䥀.>q^hY:߭83)ϔwYkJ`o~4D'bI-KY|L|2օl˗_cm (-M-KZ9+W'|/&>Zm8w3TK*7sh':s;!L})sl 3͉mKxG/zO.ϯH [T,[\ؿ_GӸo#$7w_|4#V 7HZI;Y!zeÛTwڕ:+ݖGx$Y}g(֗b`jAt iU􃣾d*>q%yyA ^j <8GeK0vGߠu*xe15r4kROgeyG4?""#veK(هLDFD]:m骼>xu6ٟ_" jэ'LնG[mj=Ҕ`ڒ4]իwsYʪUvd(/Eo闎pԑ^NR|\r#Uv:u}U|VIM`Ќ@N'&cuz<OFCDJa=5jTȰ?@jH>-X@РY asޭJݳhZJ.ڥnۚߤ]N/5~^iL;5Ulg̻{ |P@lnݒ˅n|[2'wGdκfWp@蛀 |D쵫WY/A"IZ]8__;aXNSv7;;/o߂۸a'55^u֢@8[scC;0})2A`6HNNFl6UvV &d@ع@Az ^G{0 ;YVRȢVA BŘ+WEiUگj[ϮOUj%TWAI$[- ܑ[\6,&wt-(KgsܖBiX^z#eg\m9-9gR;))ˇ3g6p@PxG0?~UaxrfFc]rpa2.aZݤ뭚+6vҀ lc*ur?oD qZˀlk\ u\gk3t&UC Xnp=y4DG*\oޗJGWΦ2Ϫ͎K j<Mq?3yj,Q}O x)XS&xM}_|#SFhݴիTjԭHok?"|LxQ_"Rx.^΍~cUI1P^W ((Iv2sk %(f0Ӏ;Y<{.DN\ueajz救5z7QOGg>yV{L׈jSNk}17:a1e9߉⣕@ #YOAz5G-N<ȃOh~>-Jg:jHET.UtN[Jfn_v5ݩFW})jaŧ?w ّ: )m$f.]N ].eyj.[Rth|hnZQD GBnABdDNSIƞ^Y17#nc]XZH IHj+IĘis^.ڇ㦮^yd$,eyhêPv}uI﶑k|M䓬!ӛ@gP{g-SIy5  H^eh=1* ))%P"6Op'lK"џP$T߁S]0T̳:LJQprwNfk Ղ6,ˈ$\,^N~LN\5"D4i˔rmn8$e]D H +Ҳ vŕO]vneyNeJ4VRReupwt;4@ToL~X9SL?u{_WnDhݑ$ރ8QpGG8lQ!kCaM yb+~=εx3S,kv{E Q}0.Ƈ[Np#BjMq(&訊OMF?NƩLrgoI5s-,I .w&&]Y| F%`X0>ϾQ%i+f@eфBФF<{ԖMne&q@O#֔ w:~ye lظm Ƴ, xRͼتO!y'O?aDOh 1zRmՄm-T%Gf|4bR')=Mm_ ICZ0m`zL7)S&eMe8nJ}Fk@ IeM"w*Y@ (L 5eb@ ;4_ƳΖn^(v Ҿ+@pǒJɞ7uj IDATK@ b#q@ \) @ rG,@ ȅdGy@ ;2bR Q(f߾ZȲLjj*,hh4ZmQ'((;-SfZ1载 '((Nb!..F|||DC$(aArymbl4Q-2oHRB"#I TIrr2ΞJ 7uu,1 t#VWL qK[Lˤ!\Zy6)A#vÎ,\:io 36 \beGe d.<3笹hXۢ_kn潀G0Ycb ^@P|(2d210&'eLs:Ii1L`%W>]:wKx37C<&Ϣ6neR3O^ۙΝ;ӹm̧>O l-.< \Y 剹.DӸC-xUc6Nmét7O )L'AP(vjK x"keYl4"drXu#4{`Z<_X|c }Bi7=*Kԯ̝i;!\8dwE]X;v 7'ϘԬUf{Уm$AZ7d:IPdxsC} R$N$It:\FhHVUZ+W.%j|(R /wX.-_6UFaƆxp|U!Zod5g[m1-(,% ;{x:% g8u{Ĩ]xZBUtH(;UK4pG)Nt)|}}3-*E@4z?)te0J<5>90I-v :JJdm,1{XrZU6;H-A}0~>"$c5hɱLw$$ ߑ|X9MWI*_g7IQH=ˊ?&al&CHq㶘eAu^za{-rI2)q}f+!2Q,·C6ƾAYCEyx"[SOxx8KEJC/ooRR,ر *R`ޝȒOdĻ0'F{ No rL&Vg\]e ۍeߨjGBds^(f@H9qzɞzu&bӶ\4.O=k)kM@9X LA(Pm_sHcbkWZկaHMM8wTk{?؟W.;]Ϲ爊N Òl( $齈)U4U*W!000_((xLGV̦Vt~ذHz}p5/S{,XX$姟 Fˋ^'B TP> ((xe:*:EOl@ ↚q쇻'@ )@ (ٱZȲLjj*$z=F}$(T- =y,5UkHC919|GQ,m1i_OϳI97}k2}MqPvhy/gu Y'd+Eb@ }ɈbAe &۷i5mА,K)^`_R?Yt>L%6mfϙDMa'1N[, ,WPOQ+sS,/.?AYkin@PXhĐeqqԮ[Ɉ19דDEzZ8~%h ק̒fY*4s͌C^Ȋ=ZwZ̅]Hu\5@4-ctSrb2{1 R%%99!t:L&#.b[9-.^ XN&2WU|$z_g>^㣧?<ÌkCx/<\-?ePvEN?1^yl#|ԇe Ǎnt7G݉O4H,4C5B Q$,'R"">RRL,B|B<۲%ǁ[*OvB7A=xv7iѴ!m72;0y[ 9WFS~^֝nzг FLJ^Y/iTPl-rӌh_`/*N3\d @0?Z-2l=oV櫽Sc{ ZcGPs6',c6uXeŝBYAl2;oRm^̦F/7r%%0? pZޖٲe3>B_'1~-xU}Ӱ6Nmét?79GܑL'YĢ]yqHu1%UH槽Sc{=C~"lo>.2f PdJk|mI&yävih1k|zݙ[]# ~~8Ld͜y&ufӶJhs,r*kHX{mhejԬKvѺZ7d:xϗ*C= Oa:w^=nǑN E$NիjMU-Mt:$)]~HJs樵FN5;>a _ט@MKP_HEU-Z)v ;0<Oų>~+_doϱhj{JD'9Wd gw~ECwg Lp>H֔I'OGepk^ INV+ҢM~=Ce9Y?v6DtKP;>4CKI H" "*^,ED)R, i[zKrB]3;73ߙۙUHvm [`(2ĵuC::x /0!6s*hii?nড়niWnń)ǭA( $22٬kX,DG7:om܊}npB=S7f')-3fs朦ބ@3Ue]89{>;cL~T®8hI{Yvs1I* )cRTt\k1~WދO{8H9î$z[B-H0q+?:5޹kk=+(p%//=WEpP0~ d6~kj%%UM +5T.θc"^g{/ mYs85 vKRNSgmޤnk2dL+̼o4嵕ޣ#(5M:иBپ.Ab^Ӥ,ms|S&d*42ɯ#P4աN !E_._<_HMM>l8Ǐ7wrTuqqw?^J޽ "5%Eqi$a00-\z͛6R1k] YZ`Jhu[:ujc2eߣd4]/s_!)y-?\ݧso<=lX_?կO*U1ͤr9BLLaaY0{|Zr޹;p@e|||Y&>ٚTEd0鉿?5"(0R}ef)Z̪?ҡq,'BcAR2HZdOn(N #6 6CyԢGIPH >(IYFoo/}lҋ`-#tK6#H#xԲIeԃ*횗hWkTI:E$}.Ks:'F*ToAQ1cE-AL5MHLLJ0xŗp&<3gdu@*$&&i<ǖ -ZSo%u `SY,;7 cJO;Y M<ꊵ_ j]4zr-+h_7>yi1?WFfǜ׺쬯Yf9 ^Lp iѹ U~zM62㥬~ MO+9܇^7)k$}-ײ֗~͐Tudvtw,gQj#F3cj,3)p! P)l9MoV'htO6Ӎ3լ\,-Ng귧I|upĞwR/II,_wKdoDd D%~\^h?#ۅL^ܽ i;EE (,BUU'999`vڅ(رŋgOIvO|\X\xkM|-f*c6Ȋ߉LV3wчkeJN&ɧX1scGoק9)F7@@K9]Z, /ۉ:A7ǟj嵿rY 3<[Gtn'&OE?A ϟk1p^d<ҙu3Z'dP*6<~k#lf<2`(pMۚRӄɳ"Mn{ׇ&|ky/sc<ܳ.6!-k|WW`skm!-k|7Rl\ ew- ik<ގfJ4O5=ϥ J4c_dtZ،m2Icرl'QJm d&US3/ܱ0`2p$;PUu)gZcbGBJ5 v3گ|G{ygP' \C!=ۋF*N?JӹӢK%{bQ%Ǹ'ݬ~zuÙsȡpj02z{vG?&6+k?n`e'D s뀁:` WHPӷTwJiJuCKuzJ йe tA.,;Ne;%])s)ηe4ز3Ibpy;#9҃;3rK[IhhZگ;Ln/a{h [}_g9ص, hs`}yS?eC3/(Ӣ>th1K>!3E!wxL*y/җy6La11?FPѲsou$V_mL9w_GEuUF7-$4-f‚~<#4 jDgM IDAT\wnA.A ĖDcCٓO9тL h:t@.^2hhGQvӎ^4;q)V|=r<ĥzlJWS痶FQ^H3KH F.\o/gzi<ӘL&~Wɣ?햼h F$$7v[cK 7+x羸Ano9GP 2E 0y4T]9ƝKyΞ\que8Vd'Wƅ3?DRՍq)q\9{GqfЙهɜMєysZ%ʝc}udž\=l`?/{p:2>O3J xeU"H)QÂ~Ml혾:<7'n2nI6|rSq}WPRq'2$ج69jv+ϷˌާW6ʱGYmH-a={S%nb_ŘT0OӿnP6`RRsԮG .[>&M֭N^ZbIan=?_zt7هJoy993l߲/'ZWs/TO~Ns1N&9&.dt_~D鉓Jte8za^:p%ٜS&dTyūlAqb'A m@pP$$rF >DEFb`p4Q\8wL?JDD$QQTh0L_2v̭8>PQќٻo{Z85v7枡ýh̡ z ݱZZZ&jx^aR4yp-\syi1/[:5L^3LU1"̜Æ$ l_29j.r|0iK6TDFJ9\fMgEfv:Hm 4_7Ү{V ``7*g\֤K|fNťدro *ek%*Yqծ9~&SVYct+|ύ|Ձ+'W۱vʵ6nxk./|ϷGCAqDsr缽2mFPD[\ISe 9 (0M?ȠC0[$;qI[6Ʀ?Qje<=<|RN|Û .cD@ݎ X54%q+hoM»y+-}ǡb;9}x{!inM Oi9RKڥP:9qw ſ力?=}Mq>}+Շ@RRRp:hZ/xj!J8~ Ww .dEhǬak С@P~1֨˨]6&SZFz\癶HU1 /OOBÐ$_7r*&RRS9s&6&PB)^+(^C^ 2>>Ԩ^//o""cN7'( OOOhР!AxxؐK{S  MWuk/aKAIiQNF!)iih>kxWq]'mIbnnOl6l66ј~Y}UyUТ֖n)+f'/KܷY"R3iI_z\Jlr vBךC3mM)bR>y ú|OƫkqoG>Zz)(<dg?LVGL|` lY ^YGVÄJ9Lx -è@,g^ÜYkǚ|uSzXQ,x'ǿV[F9NcdzsX}Fױ;Vf yZG?{&[7NT]oLQO3ſ[al;QyX>a)T[ ZWnjd?-b:WB֣;-]dȾ% qU%..[s9e:l)%ٛ e:zafm庮qBcϒ517"ӄ3F'XK\v].-_;h gSk>r7+0wUEǒ֯ʯ~2E^8TU!9ŁhDU @h2@Uy[ձ"l׍T 9Oatn@g )}•'W*ZVo]['" PTtnLK9_|2- ϥRn74 L$GAEDf9 AB ` 83äri"17^[ [F$ڎ㡰a6+̈́ 'ϲ?څ`@ݩf-F]3=chO-]k p:Eǒo<Ǧ[FQKFHz$8T5Egm@l{IouMWUB $o EFݺ iѱ}[.eO]hI2[1* Fin4NT6?z'_J Sd?G 2j|eO Hika W=Յ|bOߔnOd\|ɞx{zLWMT5JϭdGD3z`?t Io:EH gXYȅ35Ewz a2e7gI@D䓽5p< gK'ѐGIXC.MS8߾c;&N3E@rUGYN톤trLeF4r؝iֆA#xڣq} IMd`gX[?|nhAi+ O-;{g3}aovm}./.lb'*E]&FlvWԙqԎ;-e/>*Vӫk[(B=~\.JPl_S\NBbrA,6[n[lV=rŊ2}Mϲy%R]O[ՆPe5w|i/ٔó$oKKtEZN!v#2ա*"ٺ'ŭn-!l#}~'<)Mє>cˑ-,>PX[ [4bO/T1n¿bs]fPqT5me¤riׯq.7v4 ;^ecT䂤KZ9\sBΫ䷴,x{q5h,V}f#22|z"aΆC\MrLʡ y; Ciy?ER#~08H{LxTh\P*ZCA(vCQ:+޾8͂_iWpڣ9Ϗ,>}Ώ6Zre ?"N(N%.Bam4[Ʒpxg런߳wEUX[uSc`3t{{ڒ [1.:~X3-F xv'N{87gξT1詳Cj}+&)֮Eǒo)Sf$(0?n!CX8kj@Jb9'r|[xXLPn+:i1]FLj*m S kCF~~!V٫2: ʼnI9%E=Ӣn8R3e7}κu:0pHX"}x0o=߼mFG2w,54k}h|m-l(~S^ѪB0W#&2}<߉釆bd}s#I1R0yț/[L_ At>_!3EGo"]xAt,莔trIxGGrnӎ3GFҫO?HIN{eHhb/?DP` իUۻH"(( '<1Ϗ$҉;Lv33`4RN=V.[JZ0xh-Y՗.KRAK_Կ_K+$>IhX($aߠ!Tb2ʹ9|1QTRU!#- B|g۷+jWW܁,>\p6f*O۬eΫ/3>B$V^^DFD{vP~~~ԫW@ OVFY@ d'm @ \Hdy9[eEX{@ 42r51i@ (Kp$F*toф`@V8@ I~E_,cSSS1YԨ] 3ߴDΞ93فd*nS@ \R9l9N ԩ߀x"/盯Ã:p~Ro@ /%}L#iN<.6-[GbBlVكI2w9 UUoZpd#km(}h O܁|\M!vN6>y@C^j=U&A ̹LJDW(BLl,ބ_ܸ8~7zMZuǧuφ9i8ќ;o>”ķ6*b)O_6Y` bY@r}ߗY(OX$bղcb>jucx#@ (dR*{2:2#99W^ycǎaX^{2eChF<Sǹm.4xfO:!5 0}Bӎ5Ge?VA'dz4VA KE[9yM3UU>;FXXzΖmwRXB`X'HfV3uq6ku#<ԧ6_MԲy|?ct$D9ϋlq y/O?ЕʙT.Zl,N5]c. ^0 EAOs3}hf%&/[ݒO(7'l"o_B@rZ] m<@?Ɔ~Q.&(ອ1x_Ӗ7,=6dI'Ycذ/~s*&Zt4ϲԬxr/^͖}NƏe3ErArxfؔS3uN|s0t!vϏYsfc1e@sWKO]r:.%8>KM%[Y{[ kŶOn,?P [!V8i+ MjL_]waB%~^<~-B!3eޜ׈&=8/՞d ~cZCǗm:̷=粼tmM9*1Qp6.kS=G5?+jRg/hL|"'O^ϲ\%eI MaׂH2'{ 1Ƿ̷yS`R}ԩ81fX9}~ ZuZ%O&OXw͎,ɷΔê\YVKM*.d=~]ihj$[>s67`Q IDATDey}:7'1I\7<25A1T0&GLa=nzm}Ο8}4iw- *#C4XAӟGuؼjt1n͋s8=5ld W[/>Dvp>$_DXޖV  {#-Χj {#uDS @pO^paGg=uoB!4\ϏgAڳcI]=:pÌA4/ JdW{Ue̲]J>o#}6gc/?۟VMEM_x=?o;DpyNcet_J/WTAڼlw7^ '\>.5%Xb/, (i8&e+}K/BLL 2o$7NZXURSSTݥyk$w/ρ[$_`9_!3GRr5s]Zd zYiK~gJ/r !gπ]9dָA|%a\۬^[s:d|[m_{泩|uoiV|#ɨyq-ݹ*la$laq?K됴G3mfѡ WⳞ:S~k]uغ0v:׻cIם-PևC];WSkβ7ie c2LjӇ*7W V-}:e&_V@5j΀i6Ýʺ?#:,B}jy؞O'+k/{0i+!#( rX*ז5Mcmu6?+?DUUO@6HL W%wFi iJjRg߳p[^AI"*,=}7_*B#JZUʞl溬$j؉KP*SbKKH;eIZ{˨{jMeVWm$owo~|{ 'w&P⟱i* znƄaԮS[ugxrj[hh=rF ADT.Ix\W#㳞:(?*wHŗcgdcz8Mv=OX%_WuihO\?}$(Gwʦ7hh/=]h Jf;.i~ޅ Tө*DHx{h jRvXYyS/Wcvڼxlw;VnP7ȀkQ!U%L$4Ua 1g\Ǐgq: 8#"6:*[#c vǻ[稜lׯMy^nnf<1xʱ[,)Wáy/מ\\?=z2eۙzlucxjN'?>lhJGO'iN;̮qd{ܜ"6"]eb$JA2//=u2mx9_l@aU?<{FMtbgQ%WIz&}x]Eq,\s]q']_ ;cf;5KTv桧 __9*HAQ5DOϢB#,K( ޻44N'I ̙7 N'aaaL|~"1QlWݤצl?"QMi"P `Dsj0Nb$<L !11?.a|!wz.[]8[˝ڟJ|OL]Y6z\/K𑭜uI%T%sW9̕i]#^Qv7vݗ_4 巵w't@'* z,iK/[v]™.NLT5]䧻akab݋6m~ &@MЊ>{Yv5]j(MЙ:,x}jbh"a4MM[Bύs%LFUTUڦkNd\61s$Ƴ׿?s'&*"DŽLLJK/#It>DXqj:Nx|DO>`D؝أ9g}Iyu-•%ji"쩤أ8k>XrCZ#eēn>7m=qW?HWgk.33o->G{-Y*v Qr-i?ߜò_s*"dE{?LDT2i zsVYٰ~kf0\IJ%%)?/b`B,+v3=OOP'߲6 ֮EGm[hGK.`#\IL%%1CZ37M[-^sW~MC5WG'!gs-zN_ +uX^慱]8 4MK[4Y$ YPT,$ OOvw>T#>> IRyW G%,}* +6 +m/}A7-wwś|/d,j̻#;g&-wyd.d4]+Y Ҙ&1'rV??.:IRǒ=mKh> ]hv-mf\eCfCoelW`.j=eeؠݰi Ym̓] 5C?)}PgL5Y!QǦDKEQ+PƎWot].KsoGug#~P~;^Ffc)B=z谳HuSƎ"rb&}qzstϢ]zu~/`JTߴ>M+dI'=[]Y?WO? Q_.)6/ATYJl Sxṿ&tr~3{\UUY\| W{9} lEVT IH?2K/篿5իYR n(G>EwkZڼ:CJz$U=,gcf4hڲ _5kd F#C2N/~dـ>jRRe.^OKɽ rX-m\ّ&5*QA¡,[wI%2ī޸w)He_$ʕ+Tv;U\-m SU4mbYb17[͊Y K-:ϧ iUa ~Qy7S}a 4GmݖcFso,[e X?9*O_d-CT tٳZt:<#ﻟ}^vDGFs4CD$E@ p,pq|]._6kՆF50`R6נQFU&K(N4M7mI-O Ai*IX/_RJ%x$5K@ 9iÇU2U2@ 7Z,IJ5K @ g4UA*w~2+UJ2 $:-@PQ IN;ؾ8<|*ժ* @ !Pd/5ӋVYRSg@ (hW>}s~h*$Ŗܝ2IN@ P2-!Jt22@ M+/3@ ASr=@ nSV#1~w@ enX5}@ D*KkS2@ ({͒@ b8} f ͆h$I/nHfxebR&nX8yw߿%*2?wNx{͆ ӪM;6hgY#KŐ(#Р~6GP>@p"KjǧMtC:zQ}&)7._?6C (~~X6CP٧tq2peNogSxw79&ZƶP3/zQY:ފr0WW{nB\ݾHCyrd=H?wDw .8{y빟7yZx@ #d+([~dz0Aխd_Ҩ:ޞ1u5&s[ٳ֜4} (W ZoYEMKzE|^f^%T8wf>M,kHXn;8n;iLߩ#Y%y7yZR,YqbjcVA2>æ|yTioWnHl[U[#OvdYs0řWf] 'fu8ɏb6)mqX՛]bT4gsp=3{'WnȻ()#_8NOhWϚ,,uP-@ ؈ID%ѪŴ/jԦ99p)5hv,Z;<6Dsbj;Iҧbs^tYVI8i.nӌiZo8o#Kuh6YTc$TC?HhS;fL>2K=1;kwSG#4/_̯Cxst}aLR#|;ޙM!Hw3Y:4vTNA4]_&LUSYu6{@ 1 )\N"טg8oU._Kʥcxg}mF |gaA|q]ewŀG6wp~/1y^Txlh+j0Ƙ..;YIV z᝜K.J ӡ~#& I{{CM,Fxw[RvbJZ[ m2M(>&3պ0_7ъ R)W o8ZcOzSv=:[Μm_Wz3+xS)U㯞йuPvت=BJ3M;+O>2oؚs9#~h` GJsgژ?Lըbqv.+YOSnXyұ_kh>I&%$ZS/ !K[DrJWoë>fOa D6AԶ؊ժK]^mZjAVmJ*E,*uH7DEd%HBuGž{CN-n%bJRo|fVˮRiSwp{'=iq@@(J|=5/}2, ?Qcsӧ9aU']~p j*hIJ&FTݸ{YvC¾,] מ&}GJ\>$*^^fIgǪuZ+} oQc RRn=d'&llc ݣ]H@4F9Sp g(u=6ǫ4HTGsHh頻dz$-V4]V%ջd8[UK?TyA;\ KkǴ՗,J9ؗoѻʷ۷6mIǞ tFM:c><'3sƽwi+iUe*J)9;RX~czܘPBxL ? d<9*Ϸ(b=$cГjӌɚA* UZ,CmM m|}4F=;ZSU\K^)_mϭ, 1[6ZMߩ,J;M;ؓ$y;յcQcwJo{nɻT?_|ICS4~v?Mwq~$+|KM?|@WO-2{ Q?^ho[߭ru.Qߦk+z:Y?f񟝷m{/>v_|2CO?nYay0;T Ru@=ǞDIgx5݄ΊΧTⶮU_/K(O~N<תktE?GSU%}*HՈ#iSU~I~ھQ%i$F"H$՗h$iٯ~zOLyt_THYMedf@Ҫ++] m"l3 b*,!CbѨVX.ߟ s"rrrUS]Ӧ _1 NJF"z-xojk*! <{qfJ'rJ,{O$78d6]n_Ioݼ;Է};G;sae`1h ^F2' 8 e@(pBP2 8 ,/4AOP2 8 e@(pBPH/#COP2 8 ,=1S2 8 e@(pBP2x%@zS2 8@s(cb@:S2 8|2438 e`1 )pB6ef)pBPN e@(pBP2szP2 8 e@(pB@BPїiGOPĤ2t e@(pBP2 12G 8 eigeN@(pBXƤ e@(pBP2$nT>MY2 8 ͌!8 e@(pBP232' 8 ,c]S2 8%1 )pBPnP2 8 ͌ e@(pG_=e@(pBXIeiGO.IDATP2 8 ,I =e@(pBP2 8 ͌ e@(p_=e@(pv2rlݑtz/˲.c1̲,2z筷4$I6UW^~I,}}^/27N$RڰAk׭ԨчgϦH.;{x/+۠OhD%}( n'vիGOmܨg?ںieef[A7S@;YKYYY*(W8nw%:@ ̌|,rʤ`dٛ:%%al e[uTe*S2 13 K\.%KU jl¦kt\qe[ rot q-}im<%,}Zi`JNxFo׭g=\`L9^y]G#GYT$˥͛UZpx =a\ Ukzj+h*#7w:qdV[R97 )בqK0{N;JwN{G-eIlӆ?^!g b[j/ޥkSަF;l,&Kӯ%v\ޚ?~_Z9iּׯW0W?dJtrBMDkfߥ[s鸟B^_6P=7=r:x. D[0fV!כDZ U7i5OxS?X'_{B-4jFgLƗj֘TRhIsv{;OO/?_JU} WB^UoX'ׯP<|OtmZE3(p$}+B]Brl5eG[KOh+T9C8uE/R֋?Ti-o@}t))kQc, G@awE:l_]PGc/Rv?=rb|;$1sӊy>@t^VnN?~<ɶ4hKÇ(#ïZ^z|xɞuwzz(׺pЗduKyku|XjVfʕk9)ZQ W_aSV|ϙϗUʕ{hE;@Ju t'Ì5㡫4"%Ѯ;ݧWuS5kt=|X%fOo_ޠXsYk5jy鶿>g?^~<_Ѳu>RM}f~3'W)np:ܴ2@p'tjh O<-_Yi}j>t>*j%[g'=lg'DYusUo]3E薻ҁ!Wb˚5k9X:+RA%q':k#!\'WMc>Sc<Mxx?=SaVhz(_Ue4N]LzM}yܙSg[u[1`ILFNgMEGl(cw_,3Uq*Zg%;m[vqdj߷H`PhOtȐxܖG:g_\ٶ4ȶeۭWm`ol[1Oq17H\R+W CdW\R+[b[T~Dj7)&FVkM7*{('Eu[*%wf71mVq>9MϾHjX&n7 $PqGǶ}f6ElJ\=g j}a@#o LӜvcS:7!:cL:z䅏bj (/ׯKnFTUS,չc󓿯^ZcUrjB=mn=vD=QW|R J{KJ*]"nUYѼZiĄ[?~*yVAQRoI廳3TUq= S&V|ONHC Ȫ{O\dLS2Ta__DXqȨ^la,՜_Xv]:Xv}'S×]ۤ4' >竗hx3vPS>XR*S9Azշo+*qЅe{\\YthʝȍbSa1)tǤ^%F V(<,WWy?Nъ gTAV-m4ca~ػڼ}*,OgM:UG6iۊU~xSۙWC&XA&&^Oytɤh\AKmLjw1)NuGHa[kK?Q^^\'2Icdn!1v oHSOW%/Slcwܡ~dթϥ2b.ӣe *$4=;{nyKU%rUrHe\9:䩺SXAq.POzS۩vln5sӟդ_>-q)`s4ߴIpc/M ˣu?~[p2ײ>56N$6' !hg iݪ]qOutvhvZǵj|i*]]G+^^.'?_k%?'ӉDJ?ǭ~hPǓ|VA"zv FD(8VPAV2ʊ$mx?;'_km$jp fYnmTOL8I폶IןtQ.I6UI$ie鲍3idTӔ=L3ɻ0Wu;iZ~Qgyk}NYD7Mk~+A/y-6 F+OyYLċAfZt3)@fZ DtAVjjhj_vVg 6X-X,)<3o=mp]"Љ< ?`wLG˗Q.& X`%j=FVsZt_%|TM1"r]W{@jV g{z;VL&MTVXXȡ&]\BӦMi999OBYY!9B )ݗ_N݆pa-7g˶mZ6oNV'Nė;=򿊁fJ$\ѽ~JuJ^V|t+#Q>6Hе՜G1#O@#WM;^Fy߹n.}vXYcv6ƞdyՎn7GҡCٶm+YA%v),,.m&Aa"0t͛h ݮ ^[hh"*M;+:{JپkN'ee޷R:ϧM6N%5jEx ?Z7ut<K-f{3M&"v?jpPMeIԇLPK e䉑Re{$KJ+e}8]ǟ(#jiLi},*8Kf ,(*,dg}q ‚-[S[և>ClRgZiáC4F1,/?ֺN7|lۦ-JUJL&h۾=yyyq {AkТ@9GvpR^=:kGZJ գC۶=tjh~X-VdJKK*^*_=g ED7?@тnVRM'qȫl)NpM2"麡/\z!*@"nu˂ye!.ӮxËo邼hõ_-8i*O9=ӳ{OκhQyucHҒRv >LFf?T&=wBII>{{aKk9pޜBv;\ t(pA{hSXXHVV.g9;UYAvv6+Nba x/w XI(UNYLL0iʖ%|K U҆2m  0a ΐ`^ ( @MKy^lk]q^q/Ml;sM۽oquXq4Dh^^l)9RxL Z4os㞧Cz6mC‚)M-^aÆ3 k(fCB}dx}"n?ʌG|7lؐ˜/uN/`#9p= shڸqy;J-%%l6\Nbݲydz<.IvJJOG<>?~<_'E=C׷/C< rEw0 6w\x's c3?n&^L@ρ9<{@ӇA>üA|9w 7z+O\BrƜ1!<4zr^ěҧgON? )/seygpZp5OBy([װRn֍h`:]ryc٩h˖2KvL3/IZ*]M/ǮNq|5,[¨ܱ'Kp^z.nONv~-]]ө,90MyW0^~$ۻ4do{qO:kKw{]Dz c3svvtv!9 ~цJ%O `)]vyHLFu^ȍ/ǮoN<~ -etLwuk<1{3?9!ݺ<|6Ht?~ӌ\~.z)?TxdoAk*J`R?fXd Wc/c|v/q,ꦦ^7ѾM+LOK vz~HYE$~?^WI*#$IKv䋏1h9]~\7Hs˾9: oSxu3k͢ ӣ_>{gLV,Ν,I?ngǮ # s'E6,&5 kc{g& fKݡL۳{sf=ïOZSOԴ!A?>q*\QEhG\O?dQgh[}YBkv ٱ{muݍ{ٹg:CꄦKRc$oCRMHߗˬ: }! n֧W`mYl] ҫMy|o'3A@9/飹aEx9̘\{ِ6oaѼo6xu*[Fr @ k{G={2|#IS6T)1i 1.9c t~4k)>T@/!x,p{ܼƛ zc`ӞA<*y/ee0}0~o?gm%$i9&L7dԍ71yꇼ̛?Oaj"N@>z$G`˖ 7<^"~~Es9GU#ۍ }cWjgػk^ю4MQ[ópuu4a GOi]<^~Vl^w+mK5?~^ldl>{~}V^̢'BΠ#ˁ6~TR|6~>%4HwPFqz%6{TN1repgr#1]³OÁn]32.}Q^\}pc<y|?d&[Nv)M_c'|ɺá}dKpvBx!;7ЛLF쩼S_4LE~»=wwS Ưh+y<4Z3N1`mvU^I#A:/ s1E^!ÂRJpmrnBz:Y?jr^)wM}ɲ i57IE@;֕8 Ąc[nf<8!0.4-^|^/>_yqa$kq.=oqB{青I3Cƴlԃi 07ʒ"|~/nÅA9S.[y|ʆ3]xr,nhzPZx oqX<X0lǁ}\0E&fFP Peт%,~~u?E~"sf;6sXO'Pȱ tCK>4#D@ &+6BQ`,D~D*[}bM$t&)E(jckO婯qEK;ImadLYOs>ֆP)DHpLc@x[LVuW/2n })~e8 B* \e0cOeo|6}J{)QF{G#%Em۵#I}AHNd8bKHrqzdyL! )-)pن XmJw};8۴;;զv}'K}ϛ7ٳ0/h0}%\yfk?lIcO[8+c7`ةcK³=uLOAF=P\\JVl&=>[7mj)iba-O/&]!4;sgS! Gk\~3H+;B:oΏ>v!dqa1vƹqR&Ypf`=Lw Y;SHkG82ت]yPf_y[gOK9PW5t._ |~2}lRBmHVGf`,8zge[n~M2O-$-"^fϝOiQ(8. dXle{6I[Ɏ#7oBz=9dVcVV63$vŌfҢyMIOO[3Hr5Ynu`$e^ݺp8HNlwBYvn}`X̬=5u6`ON>?*8SXn=)uꐙEq? Vm xK8r0e9 οc>rprN|3*?@6n;wz&%i&/ݻI&X57FNs2u(8; 7;ȮCEA!LOa5!*);V/mMIeeL% K sul` xqPGL%:uӰ[M>eeN݌dGH<"K$'*.+1m8RS, \?&TIXL@US)ivv+~<[(a[=;k-/2c)UU6EdcƏÁn;_˹EG _0Y':g,8RVLzE5Y< 6[V OR^ݸWKzcgL]ٞ^ƔDzv%x HKw*.+s&=ױ<>Q}~rr~zI6:u͂͛ٓMbX Onnmӹs,ߛp{|ѝ&!N,ONn.7l %9>\/ݻwI&1a/~_lHjr2hc&\.7ݻwqXT"{K1a0eK ِWS~{-;#MyO!o`.j1鵀z9x0;vpQZhAvÆ]n;{o߁f͚cѪ4_EiRתO GMXfծcpШ .GOcOHkL m-,ydggGXPp0;- IDATAL:tHͱd޲'GhѢ9QA233hAęz=^<Ȏ9v46ڵ@fMbG\`NGҢeK|ٿi׾=͚6~[&>1O><ݓS;+ӸIJnj8z(Qppk&iuҰQC4nBFFVhC_sQj$nHvm&?`H FQL# URH>vt/O갣Q |>!?/B,h4iBff&VUj|搟0g!4 ,#ŪtjoG{(Je7jHSQժO c:a뤥ѰQCa+φ h|s!&5̦QLq<^/8.^VdTl6BR*TJZѼ'WsЏU&&CN8ALK@.d~pf%V֠PW͋&YP^^UzzNݬz(?™ N'>Ն#Y0,/0AjJ*65". χjaw"㣺B& u܉j9V {(_v2i `_2PWS@0U-%a_E`C螮4O| Y&2Pk $jRҵT5۸GDak$` 7<x7؆74jn+J-Y 'uq$Ofu?!V(ORq@ yE$+ɋ[Nݪ"&rA&&Qcšګ|5;5Kjviv5^qDqcjˏfʡTtQ&%ѡ*#y:#y1Jń},_t PG2Gj$3$LRG٢|v\='DG\&@EA&ϓFبt'D nd7ɮ@%EI\YBf"j*9!H hUhv ]_&,ˈZRF=*inR?h^}XB1ϳ^1~xل oB>5CjD ܤR.*y!6dž~HUr:F%EL)/ˏx҂ *u)S*SU828Z lA#]VFͫ '5nvJ3:`xڅ5ӆ~Nu~=l~1IC_ZKG6ItD|}T)/i~jN/M[VҶi|^QOKD.~4ڇO 4:&!/9m'G+-?D1mbQq#_zDTM5pH r*m5?A|pdD@V]UUC+i35-P26KjC?bk_V"x/IȏYIOa^8ZE '%TԕNcń_>ZWɓ$?'D 5 { (?X"} _Zs;7n/n^(? ~qGp!>$RN 帘~r^3Wí' 4>{F1kTJY_b! .$Ț! V>blbX@&'ߓCIP.U*^O\Ib"Q|l{˿:`V B?dKȶ}YwjQBlK*Wbث!O:'VtA|d<P[#3A+sbEUeTO e$}X} [h_'aUM%q<{G<H+5D[Q陚l((TjL"`W@@\Db{]1I"%B>GҶ)گ6\ Dj1e:WuӫeĘjR"`.Ƴ;Nf # :=} V >W0mE,eO@v[Iр9UD*T :iLjqǒq"q1 4 Cuk*2 B~eR "{5E_}AeWay2썲LdUY@~8_x3hdiA=D [Y Ro]vc]GBS]p^ɨ) Gz?AWD"U,Um 6J=(T5p'PH2Si\5_`Ċ # w?aaV/9'^&j?BzR6FT @iDZ~} Q㸙q.b:~lEuU /?bIH_U 'jՓ4eJ@ڲX2KMCUeRCt>&ޫPsp?rn.>QY'P ϴDDKh[w9"ivy$O se}с VTNm8~̫Upp5'8*ϼ#qJb}-TN% SLdQvPQS6AťF cMTg_U 5׊^}q*>,A&KGU?4* TukԬczFiRĜ&s"j\]P8S,#OTr 5FO/͊of2wZ;3IiYҦgJU2W?5h,wgWWs:_<֓IA6V}>yyڣ^פ߃RE9Ѭ;. Y.{ڞ6KoE0W=]U=z0nDR.~ t繹%}ֿ U(O 9 W= @a~Et|l|tƎg?YȺ=Axʎ"![\Ÿm}IR+ +4,c9QJ3sE DPk֗DUקycL裣 rkL^soo,'e52*~B{E\䁁4~.W-y$u!rEFd%H|wdNH| 4P>ˊHRL"S@1eR: pיG7y_1p̻x5((pBfP= *K8|DfaۋK9EqF&Zւ<3?-yJeKlg)a@] Ub)Ky+h.k~k֙~Te#ygi X SwS_o>Ɯ)5ֽrխ72uqkZܰ#g^4ۯ=l+h'C xrؠP<7/z/\~ܟ7sy\} uIKl~_~dx -8ʔW0dXb Q& ߽7oj48!xvݞW x1_Ry)NNxm&z49{dugZz}j~Kiؑ3o+y 4 gir)s*L=x+Pٯi]'/>Wb_Xß s^LxWӘz@r6ϸ^MS]td~a7z8c-; 'sW'r(m<Y(P! y.T q;w6 (J| yGxAٳ+^>>/@@/ #yx>C%jR)%<5!ޝ!pd-v'P} x͟@ZFtM#~}ȌxmrNJM[~S[ٟ͏ѲIfFv̟‡,=d/|8|"=m @/*}h|b>({гCW1w} n7/-d%,YSX[,ykw|h!?|)}u2 ]liSdih\fr-M߳ Bj^݁`˖tY覝PʺތYx f}s'^Jȑ>as;g8S@Hg>< t @!_}ƔosW>۟f,=KC}Xk?`%px8v?`9mIV%bn[OOf@^4Ҍk>\eEskio{Sxsֵ`, Lg.eɲ,Y6~)HFRڜgsk7oKY{q<1e&sgOpy[ 8k( Ȇ}dӗ O2ݫW4NMIt#ly ?uMX3*EwB!PS' /JR 9tchw+=z>dEHi?l #l !iJUr{v識<^+.Q}5Rkjy.ANhvH^;;@;XG.6%J.}A@nNK18!>=VN9%:D@p种NW=r [p-/ owIHt.|IFv={܂W]N_e{q%Xixe&e͠U&-eA'C҃'dXZmJ* SXNW(fKOv>/~ A9G*Y+,uӸbw,=PĬ.bRr]zZ bdt 5gnޚэ3C]o$w#|5?, &g-BlBKʠusרSvY}kIؔӚ;4"zdEa )ȢmMR֖Ֆ@^E!q+ѻeė9) @ope ]7K5![S>>_9)h$GhŬU(iM}uʹs'~x lCfrU/-\x$\pS\^gOl9ŬcFҞgSOkrQNN{+2 nj9T?VkA{k#δ}{/~)z]hTVdE^>4ƉF#mγs4DBv+],Zo><1ےlA(Z o;<NJj Do")ZRLJin R3I1=򶊿0T- 1kT]=>oyyssR_}I '`@4 OOfGpھO{&| sJrj2҃kr 0 dn(`2$ s5,&y}yJkX0My~ qмy,5aP'܎-g޲͔KcU>f*>~s&x)DM+9Ć5bm_8d /^=pܔ/}AڟZO):2qjX졌PȳƭLTXucPFT|׏go*& ʟ M̏ךE[n{)<3nv~ѮSU~`tC+=pYm.gLH_\/WSߩ~()mΦi%pgt̂z]ҚI|?09me4WmƲ֎XUn[v>}?qmԡ7s[5nZѓ[oM]]_L1t"dD5.3uO>{GL ;: h}0=8q ˚5KϤmVPrf+zG&}zn&I.cNm͸x%OӨ~&\ڗv:ϻG&'Xcq, 窳ԟfqUf-&'VH*!uiVXIm3GC՟v$P埋/@S[.WscdAEuaHmՍy)~ܕe=ͫؽ{>`»wqZZ ԥ?ԌV%,.، O'kd#U>}x M2t\/d)zq0 IDATJ^;|0=㍯6'әr`G^C8U fOrl~l '2rZG26uUhX f-]ǟ%L.F>ζT˔uĕs'T@ lz%|X!62Zb𰎸T\Tk_}0VvK2_}G]#5]z .`@L_~Tjowpb_zC'0Y{1Ao0d1ƌe?ׯyGӅ`m¹xY*?K/bwmF FVg؜[Bw^ɌqX_@[\kdDЯr=0kC^^YW>f|;jӏ=fW~_0xUh`vɘDji2EZ[YRfeqкA2l[2g/GYr/nl~Mwz?1{k,]l,V+TטèoI7u&miPwk񯔑s[bXT6R>,_v O_i}U\xj],2bYKy0O:Z'DOj~~Po^oThއ=]|KF†gZ@R O=Ƶ]C4?BƜvapՕ@[r_%K0 qC9V9j5Va~fVhJvf6-AyޮDگs?DJpJ4?NVsA[9qpS*^z.@\CJZd/H9)tmЮ'TAKY] }ϥUr*ЦMbokFzQC}ڮV\5Fll*(ؿV0}=OpuCNFdo|OTC3CV)ǵbvGa\d]Ɯm7\Ų@A\Y#쳄(cSclKbro*A@i!{S7 Lon }J]{<ѕ8/G ؿf,fctJ퇓i*C5!#Kus- ݣb~5l ޒ9L{a ˶sc-x'{1r3[3^(g+_(wmE{+== 8ʜg'0}~r7- oo &ҖU_?-wSwUt J]kyqLݕ4t} d`(/"huCNFd<}9З"؛R>]½ߩEG`,4 ٭8<'ݥ%k~uי͊FVz R}_2 :`@`?{}F&uC{R蓇VPHp}AwkʳPEkQ}])+r4$5}FkO %RjLe%‘<~|kCNVy0喻r /|h'k Y,bސ!C &/M0dȐ|7 2d FToȐ!Cl FdoȐ!CA 2dȀ!C 3dȐސ!Cj?K("lȐ!C@nD 2T dސ!Cjǁ@ߏn׆ 2jb`6Wo,}u{^$7oIJkC JTz4ABl!`m(\2dPQjjڷ-|XՃj}Ii)]NFYi e֐!CjJdYl۲̌j[-B~cNJFeEyu4dȐZ2իDZc[#PMN@u3dȐZ!C U򟽚0~\WaȐ!C$Mzw2& A8194dȐ!В7u6!>Wdwޟz:9e2@CXnIIAƜ!C 7dȐZ  2T dƫ 2d(qY 9 2dfT]|6q 2d47dȐ%h,Wސ!Cj 2dP-Pp:.oo 5%Rw|{wt!KGzSz/*EDQvIGiPХH"ҥI\rKrI.܆O7o}v\q!B؋qI'GXf@ 8@P^ J A @{@ (CXG C{*F@P^ J*oBM׍,?}AI6CnF?9_AZ̠UW~rRw3zjz-u*`ڶuv_P3;?Ǹ3_qR"# 1]ȭv/,_ǎr)HxDMiQ: ՘sP*&ͻ=Š *^pr{Nw]gE̔Xo❤|/G.O/:Iɫ;]a\i[^KpV% F}VJŁճ9jOA#Lmɻ yDz$Gso_YoGȝ5ǽ2q\=7N`1|Z|"pzLf9n&NՂՊ9ki( m19u7+(smB:j<-}reldaÌVW<9cEy@lۅnKx\vIz\ذʘ>r_j@C#OtnuIJLחx.?8FLF]Ưؿ_ ==JQɈ_/=M 2%P*㹭,GI7PF<\:߼n|I2.ЪKܳ67uuLA|B6ý:A~sD&/rҿlk:ƕd f3:=1[bK};~9Iٍ:?l+yLɤ]?S$Sӌx+U+-fOފΏbޢF]kѥt:4a!HR2邒A۽Pr&񜘮GYJU0 oi _.;_کE5iGLcErHw0g ϻ-k7b RaLz>ޙ\?M7patLO敵|: 5"[cǙWIÛD,\ѣe~佷7Sv,X n/?04nd붃Ws>Z;VCBLh6˖/c聜z+8Qubێy>yrb"fNW9/uSXBCN:ͶYx^vb mɸjs#Z&'a؛.)=ޡ{}gOl}_ZoPMީJ]zl[zV|{͡+ Y o _"E[p /MveCb D́te,^!w ;ZȎIbDzR(FKo_eNU**]:p*2Q>VR_p;]v$dy!oC5?ːŧ 5ZF3H3?/c0m$o.O1)%MMy䥁:ΧףFWO?FQig#kts"R|opn{l_6&6Or ;Aal/ @ӵ<.Y\+Цq0:2.EZRcV ϪM +=.dW x(z.ڿ!ʶL{rvrDs-Q!Kq6/Fw =νs怌=K5J ԞnvT tn/Z_\45;i͗,m+#YդZ0cut[fQs[Rw3xd˸53hK]q>i\>;Kf/`]7&EhW89/|0kg)muIHWRMEs#ݓ>y}ɯޔΛGr Z7y̎';é~sD.;5|ÛO>"jԦQ.t{ 1xO_z Ud}'vga-].F Zoem@灿k:Frl#q y%s) ~61j^-h݌H=ШޏaRڗ8 wӜ$ꌜ1݃ӽr7+@7#דeeV`W999|D9 Oxez;^H"9N?K1SeJ F@@Yii32_&>ہKiq䪉9ʸr8kVUYw*k:s FP$u&7^grYX~|m[y϶_/brJO-Ʊkd2k+Unr_vG~3{;;;io2sLL~Gs>Lؖ/\ݵ*)fLWmJ@H~pۧ %ײ>AyS_gU3[^2NQ&2LPshwQ9ɏu'q|$F1o`O9 Cg&.qM)DWr(Q=4~/+0sQR̘S8>>\2Է6f}G)~X>tT34Y]>r;3ޜʷ?22Y r%'|ݗY?1+yVu+p;gMS|oś}U݌S. <,p)1Kn"i]ʢ/*3Е%#^c9v)ca$qvEסkxtnElHw&i)QU-V3dO˹Wԉ,r*I.VkIg%l*Ųt{z(խ2q),lCƐぶO3@xym:~\+aD|*Cjm3b/܎Ar_GLtaiՓt!ToͧCv#>;CFH2.i[}|cTIË3jt( _Y6oa}M_m;) Gzgen6d|;KFO5k3Oa0D<2/嫙|QFA4T#;GOXmZnq-K"Ew#VzYuG^ff&gΜyE8JY vD_~f mAAz+TbTZ[7 zeC襓?B=C̞uSj8D.7w:` Q77mnY-:YywlqK(G1Ty#ao7ma!w&ŏWsfNB p^ ) ZyT;@PtrSJvӺ@AhkA- s >{@ ( @8{O FqPCjmvt[c}ahȫ֠vͰ\ަrr`&r'+ҡH}$(:j&gvƍlqJۙ(]ڕmI.% pۄ]{СN\׾ɅR5hڹp7f⿟Novtxs-a!HN R!|ޜu'?dѪulX5q蓷0 .;֝2䓹&N`Dl72gFg)l-,c,|qLTՌy-mFʵ_:8~%}P1mؕ͏r?ؼw駿)o2K֬a\1Yl#2y֙7.eY6T9UX~5U3sZ֯ϨSYx6\ɼ/u? a5Mڦ-g—GHdsLx}>c׫ذv^o˾MJ+B[RV߷co$|1]Ӟ4iӛxKl)]e͖8gԣ=:Y9fl/ʠV֋?ʭԓ _HIv$;t dH_ 0_mtz'6MlN5k#)m8+8OW$^ ՝Kʕ.'۵d>1zYJSpFޅityUit)~+.O Z_3Æe۸2"],Kw[Sel Z^TkunAMch8UCK,:@Yܫ cjrSG- }FyӃvK71Odcws;Xs0r8To.'uh~ԁSJ'2Ԣ꺹\BqU1.Үr02rr5hEȲC\Ͱ{d8v/]71HG"?̻[SvFO>֞c#s6gY.& ϊIs }=@<}˸ދ42yiJ"]o5ʢܪ־sD.3yΠ9*@Z4lى.UQ&;oAxbUP?=$ue]FK^9&Okg{rjKYd.eB)HLis%c>׾ z7|މթ!\9 Z^jX{%w|]ӉO;\I#t4?}oBlM)e0zʶl?qw7loU)vd$5;I!%˩\pSkz{ y_͠kp>S0iح{X^܆ID_:ϙ{;kjeySrJ FKDIyv x(䦟x5Λ@7#דdǛH{_h5kMhW(wvq y@V@dn%Oк 4]ȹsû^a]EY;-;XJ mt!-Tj?׬wt]A*qWt.ߺkR!?L!\})Sě^_dtFbΑ̵?~%J3"ՌɬEo.cŽ\Ȫg8UK`o0EQ~+(c&X^O~\8Es#͌9-1eaM5ÿ (*ѵ+WLצfѴkiM`֔qF|i41+&O'n4c6^O}82Z;Ruh2&Ħɘp'>8CFea{vtVCtA7_%Av%V;p­>i}GYFOIǓOf" e:Ǿj1/@\&OCL[øN}{X?a)4,=> +W㣕36ʀϠp۔g'u+kQ&dZ!M. Fu5edY.8Cdffr9{ΝAqBLڱټ<7sPޥp{e"ע @ ( z UjRT˭ @hXU*y63d0̛}~rII\:IhO 37]ڞ;\>!ˆjemB0 fTEAUݩ3Mp#2r2 z|jDF( /YaZO@$`@B$:L3^GbZflvǣ)4v w2뵛KNҡ(?V@ ȲCYON'Ȋ%$@ ***:v˸jhuȲT@ PUdEFU(Ƒt:TU$[BEBe$]:Q*$ NBVdgu A GQUEq$ySBh:Ig+ Hfq (lc{@ u$UEB/$X8 ga4VQ8"R C>{Mqٝ' "PUKUݒ_/A IGU5_aYPRp͌NC6bP/N%G뗪]LU-+_f#|@pH(:7 _ INm"(Nl!4M8Y#{@ 8"k6v#{)+^ Π*$8v}\IE6;G {*e^:ž2:Np@#TU8G=h@p@pe۵{F]f^f( `6gjZ { zzT q23\])Q// AI#-5kWqm l6$*W}D^J@ \N8l`Lkf쓓QI &%kV J)IIHHE#kW=heY&!!o"\=@)) #!!__,ulǚ()݄,KMPS EU-K>(%P6Q JY6TUN/,%l}.8{O FqP &89v9q@&@ P퍽(*Vziq/m*)n٫NG8fn@ (@`Cك6xV{ˎbQ.òK^]aC5کJUu/;*UNWG`CK4r#chV>{E$]lFyZ L&Rt6@?rsFf 1b%d,I[ILINLtu0 +q쟃* 66T+_s"2 x{{fFL4Jo)v)hXGL(*Y;Վos(+оPtOa);yr\3S (LZ YV鄱/qhS< ٩M=I/utƽFbMļ-pܨWUϔshÙǯzB?] CI 6s)ͶLQX5?ܒ{ҔuN-WaPOoqUJΥ_LݖȾĿϾ֒J1mKHɓ޹:z"L5|h#{/OE*ay}a5 C<8Is 86q|-a|^7q/#E.٧>>y~3}ˈ):1_DOK:Cٍ;d +xBa((~Y';CUKVh.Vd o5>F@h3#VU^uHASQQPQ*ѡIN9Zv?Ny&ozJEi,~2tH2gϧ5yK仍\#jefoFšCy1̜J"11q#2FY'ڗIP">ws4/ J16?C.pnA6|9>thlM/k^Odz'U #e]rY: 5ȺWg;kgd=&\$@r}2(DžZeSx4aMF[T[7w]/TS;*}(-ѷ zO=s]1s\/TT%>yun@ןYgz'k_^^Θzt C VoLjm$lϜk#F0~xƎfٔ+[7'|˔8ocRTj܁J-U3W2p*HF|E~яMzȫFn<Vga|e](1ʾ!Pigذ-Zb%@E}4ƨtedo5٣rI9[dLs)WOPOQȈH9~7]3.gw7fVqHZZ5{ 2T>3Z5cLMY';j7LƍRUyyD:ڋUh 9 Id3 zNFл2vʑvx-{;0My9d3x嬋-OIՁvRo=^-d[w#_~J&YARP'*M߶+gA!c⊓Tx|eTEjJ۷,_!/ }$%nwCG9ʏS{5{1N1btK8!*9(^Uu2i1,@l:/jl La3D<چ.}q_œ#=HԘp"AZB.֪lO΁.hU*L(8YV{ao&o|]+ޔLjY|9 $1>._YH%+i>kB);ɩZ;^&:9)ǶGѷB7UN-_k4i¡-,,8&uKLq?H!o|7sEGu$yo u #Yr6֥W#?$[Ҕ9~>Ntj&9<&sM!u/?3NzIUl+lOΑ?FIǔ~=Ƽx嵶 o@Z>^1jy6Q&ztr{IFzYz"|J2fΟ;O^r|Nɍwyu) 4җ'W;'G!vv& 4Ǔ`iM<8{: /dDew~?3L]:}ZF!^/wp>&|?7ɪɋT>S-u@n,[?Γ(T}@J6˘/8O={? gQdL0j3t5g22zUXr9UTڗ@z=a+qj*UK] #E^^؟?#zswdnAMh:m2Cm qc^H켰u2[c X݇#S Ņ`|Xr0%^Oذf5*i`#C4ZR'2S9$4zV!YubYTg'zS9z3jt{DLЖXö97Yra3hT7_{N#NWG{<2y;] (@TT$z*Zؗ8^ryݣx6bBvye5{6dߞh۾#A$%&ܱ[W7v܅'z{"s8^zRpס$|}9E6mXO -cY{'DEFrVtJA$IO%sh=hZ<<(^kWmO$%'* l!$8RIλغ(닻+!AM&<< 4D=h% zLYC\]sE-%\w$!I"+>*`Ġ7d/^.zEFX B?[:i&: Y՚@pK"IY{C/5s J:ɲV b٫H:ힼ5tc"RL @ōu{IZxQJ{qСCeZe/,C^N Z_] :zQQ,c\B{@ uTEFKgg/I(Jރ#ʪ,9HYSZI ZqA/I(l n@ pEQk9Jӣ P< AABU$v{'"FC6+jYG bI:6l[BIb 7@ 2*#@kZ%tzU5IBaeVh ZI*_R^ nI,om8Y.(MK@p먪* ncIBT@hRUCIϋeXo@ԛ8Ω4 ŀVC{@ ܅X%&^ AQEF@ (9\\t]jg`6ѻRl(֣7W_W FT5L Lff*hT Aˋ+qifA4ك %9ժDjJ5Hjr2PlgO9VI4,$&%MLTZ@ uҌFCː7z3{mč*&S*@ p&k]!\zGH J4ZГ'O^xV gZmGJ-;;ϙ_B8 6T yҡe4ƱͯG}q?|̉](^]V*E~ƍL Q8e K6tl:|(W1=FJw^{]5YnͶ%ʯ"%[B1G~e7T2&P<.ZACL8\,uѨѼѐ* )GaWyh[ W_t'|!ISZ2Rlú / `0LbL}ŵ45m5v í#gC.Y4ίʤ?bK?#>LFe3Ɍh+Bc6nm$ Dpb֩tw} [}ޚUf>jtԇ?Ԑ(y̓s‰Ҏ4[JP:|n+6`&׹LȳRP8oˠB-z*!xc^t HX-MArb6K>}_f<jޮ@I®Y8W䛢Z(k5aZULFGhV/{MA z6'x1e--p!' AIɄ0C# Jyg\ O_lzƈμz̘2b z?3qJ)  =DdFGp?Mo݃I?j3cED|^>!*)^0(]^ƽ U^+:/=uy+*(%L7w=#tͺ٩w Ȕs/귡aF͌awSlNkG6)\ɔ/b{t sYH|e~3,]TR# Z~Yg/5 <Ϧ~"?Fk$ȶy-*qzdDB㮤%ꍛT6v aʣGty 3gҋxϜO:wJUaȸ1tKq[ͽ~H}*'F8vC ιH?i؃V=nŪe;/)1?}g#(@~?"}ForU%dBVB'ps*n2*L>ǣ!clxk!T% rA݊Ց++pLտsrf סm<$y 떯C3{Z=~ny 'V1=+iI$el{p'j}ZEepdl%[8p9|x}y/\65f VZ=կvq[ˮ ~T}'+'֧Gc0YuN4w٫1Wռ;+֕j)Y/R$sf|9$I qukQ3sر};g vdn[oh$Eg.r,?ͰI#sC1ҩ&N|~G&Ͼ C|3k&_6-2/Mcf5أ3#2}6{ e@wf΍#}b^\=1*@pr/q-`{VG*uۖAߣ$!B7?u9YwZ/pdnt|I"p[ҿzKKi1qϙ݋cPq}WKmD3*Z}BҨ+UY)IZ0yi۪b8oύzm#EmS~sL?7TB./b>\W7D64\aw1r R঱/J*LMQ2ym91X׏!!eD\)Z([aFάGژy8ԵzQΩd2#9t Ɛ}TgŞ :#W{/Jn>-\(Ӥ5G$p`Vd͡ 2?5?p]h(Z=Ψ$gQ=dL*I=B39٬ߤTpw#=Gʷ+8ktTR] %|c0Ksj;wMpH ѯ b#Lz.nE@P°{ /~{nU 7/jyjAynxfFe}c>JI1zciTP]Kt5| I2 =v-ӜAo7g`f 1W.rq迟cN) 7*9 orMY:s#=wI#6%oʩOMte 3gMc[I 5p b#R{7$OhOd}{ ɭ uCnx"jTNMlHmʺIHna4οYXyzw": g~2&ҝһZ6-:cOrO.gXoci#IN!S&OQ;Qȹ)Or_>'~>I;imu^ǷH˧ёNVUW,st3x.Yά14xtM~ ;ĥDTO8[&1axMgFoiG?e>+~?ʅF2+ st4מXq2.OhwM?=c8pJM3bIb̘b8u!_[n#}bKOo2ml,0ϪW.\@ͷl6stܕHT%Ej[$ÔFrrbz}|KwAhh0 ^ͷk~wQU?NOTz/"X@QwQlkwE]ݯ]V,XW]bE! HH/3s1IHLHy޼3Μ{̝g9~ͮmn5ϖ~Hn[4.j%9[}޽=@II ;vd!$'V>Ho>B4BCCB^ I) AzWB\$wo+-M@RRW\!.B'պ^NDx8nvb)++orIXX L.&UBщj?{]q\8fu-jYi B8kPmB !Od<(! $ !D'! ΐmBY!J^!: B pWP\R؊l6}dž x}\۸= 5"vKbii:`"LyEUi*JA)Rc:682ѳgo›W,<"zdžu 0((,$<4(#"2B""#XjB}`9N"[[_XiRVQLiTMv7?`ϛ}pƤg(&~'N^]_zm{Wڟ1SiÈmX:OQo-`lצoVeB$ͫyܸ{[1+T|_hY/{$Ѫm{[?T -%y2[,w;|s/-/ެc sk5|T eOosb}&wD"4w '!(Lhxc40K|+Sqh^% kH,L*|Pjj~$FU'i]k!qL~[6٧$`jiu,w߱ѩ[BT ;';c-MO1Nk>We_ʺח´LJwK c.0Q%?1r _0TRZ$Bj9XE=fN/S}kpp,\3!, ௓ұ\O/{y6rcpog^4saz|[|f I|a4Ngy4k{lq3U ,v~VSᾒiԽ]:74ۖiq;[O5|U@[_vvP﹬l,CϩZ >WKT>nz W^YG/a~c] ;^x1uCNx|,_ %[sٺ"lۗrȬ{F=.(fY.{6fO:pk59/י(EY}/ͨu+[}7-g5z.@D$cݗ4_}=C+hƩmLIsgѯ؈ lg%c}'2㽟;O➥(7wBś+*x?XPE_S&_ȝ~zFTܰzJe{?^r~9g֜O"NF$Iѧe Vxz,s4a-Y8fej6KW1‡XW@f/QT*[콌Wa哹;xe~$_W[n߯ d}wAaa!=ir 7J?vŀ5ZR9mx2b~^dD2zH,z6~ ȪЉ~I[?euR w 5FrҘVs(0;ә2, O5^yt_^yovە՛Yǜø;ƺBiSxmO;fX<??iZ0;]qrBUsHfc#qݭS:eoG%'g?>(<zQbjJQ)p r#tfneEke;ൺ+jPG eJx}L͎e?aw+]]LcLLޏ_;|7ԟʚ7d ѣS٫CG5$%̓bw2%qXcұG2wLN'V97#9o>@TG!=Gyb?9栮'ŀ3O<~r~B+0e'h 2aXο^_+ ǒ?^YU[`zyԲǚ/Ϛ?e7=ay|6Ɍ~˻}k[Frbo|o?{H>."~ӷ쮨_8O?!F߁ͬxӷd_Ln.Sk3\oͺ )q{Kw.gj=_j1d_vt^H暫Yؿ/)%ԹM5]L.9_Ũ*1I|`i#҉-k r{*۸c&]uzHgH;[q9NTqܔ $LFݻxW;6<./[ܛ?cch7 =!ڬqG<N՟=UQ|H/?u{dis~)h8n> gw/~]U\TU=;ܶEeڅaxx#^;w0yTfȳڱkϿy(!2ûo,[Z^m:۷`F+װXһާg&1-RlֽE[VS[kqvWH%t7\BZ^za[NAQAaG&BXa~~%΁}&?@kVUEDĐA ~[tsJ^c۶p4|E k8㉊^ 22 $qQQϫHѓȨhG+ F+*(8x;Q\TDLTTeu]'22wgK|OlL ܼ\6nHRh¯}Ul u݂2S vrd떭^Ղ"=5ؘp88m82}]#:)ENDD$ %TTT`AhH(NCZQJ)Le=Ai0":/tVǪ재xE/L3pU8Ҳݢ!<#4 mq44-o$!GH|'hf^y<BSJ4g*4W]T%noegBtF _@*^rB2 t] X_TO9BqF$hsi%@`GjBF)ij+M1E/a*M TIخR!$]/%!m{!$ !DGV]X*0Mo3&]BQfC-Cjw5 WQ y[[nJ܋S-y 4n=zRRXHNV~6-FHh=zd5 &`RRXHqqQ6+JIQ!̖ v à0eKJKHLaa2 ^)EerqB4M_\ P< xoU!82^!:kC44|݈B -1T q$g枫cCmD`ܩp sRNb6#CZP&B6|EKbrI8IɃK[GTbhL,n!?3}_}_}W}Nk3[y*ڇSU v~”3k]K?ïeM7XJZ[@>KMYǩ)f`שר:_ڌ2|7P1B&tFrέ5Z*XmKbqD>K57]WBIyf7N=K&ͩAjq)S^ ӟ7DrlF[Q:!g;r~S+4 rڣנ"[tFvڗ~%C>ε0,@Aja_V K Pz)ZD-JGi%=-ΊO]xb\߿&q.eD|uij7sߍKZ'J+o]ÙT@aR l&gΕǤA!{~gkZoyא ۾cuJ"~/c'2 :/85B/@0UhEG4NRYPxؽv9-͡9ǎ=/=4po\?6v ~eT!y\86T,=]-[{9o hTd-mLO*dn}{'M'_+Sg6<4k!}鑚Bz~M?zOAWC <ށO㈎t#T D$`ٳaR 6@gd)؞ʸ1[́8 B̐Ŀ, 88eB2O5h҆W=<h8Ν]lW>\8iS UWӑhR8 vmT`sy$r氢➛?[J|\wkTݪe2<_.8T8?uѬF9۰6=x?t=SX$g/F?׊],9NcTܡ`/Fu}k|2p[e*v,˧Ce6833IJ'vDV&K[\cӵWdn5T#݌2D$`|T2&~@ MFoFϩ\Q7J#ZΑƸnټ vg3[Ht4c}[i!Y1ق2zG9.RF5gѬOkovL=o<<Я}uSFig ,]ALr6 EWMԥO bڟ1gr׉i8T#ui_IbM*;sa4]^;w0yTegLir!đ-$ѭ{梵XtIN4BtB#J^!:]BI^!: &Y{!hPi !D' ^!:O^"Y!h-1T !D#BV֫tz-: לA$j`*_MBN s*4RSUPndY["co` $ v&$ !:14>ḩN/N1;F騝]xy[(+=3 !DG`_'c(^+;<612Niq6f (=@Uo=B6IBCb'QR\HIqa4QZR=Z1POMFk^4 ! ǫrU%F$SVRRf"\:٦Cè}UsXZw){JwsiەE,=Y9MaFRNMe\ÙpfBtAz hfH}mKgi54CΪwy/V"R3qŜ3*ۡWųo K^~N^82qŜ?>3)6>rTn\.&6M`esBO-,g1o~b{ ǝɌKNSK(zEn3~|rl6^uIq볳I:(lF4sUGAe} E&>b.;4od8ጳ8c^i?4v |RgF߶\_5rI/p31+I!" >3B<xs{O\2—_|c/cP3/`u8HzCu1;{/E yl-/ԮIo{N.AhV3s7w) .I!拨#2`bF]ZcT3V-7ISW ۾cѓu.w++|c,OHZ4ey몂8ڂ 6P-k+os4f? _V6|n.ƝKVs֊w=6杕j~^ 2z9h=T>y*I$^~;o

ڢ4N_ЫamNcEcUJ1Ŀ, hY hxkgR,Ms&n͞‰`m]GM 5uTy߼%߯'8TT@Ym)[4?j>d3@sqL#$uqE+h;lqۋ8:hOBM8"[m-aw* Y?pP*!1M aͷ ;YK(~m ^K b~r$2'氢[>?o #.a'XsBٝXr< 5?t/7?TzQ'0id7{{me? }TeW#MԸOݒҬJ5 VN~U'(qPZR{Q ʞȱ.a))y-6[r=`KS]9wQq)KQ>}CGiFLU.5ػO13v}z~Z2?^F68ʂ25MIIS ?4_ߋ7ʐ\V/@zNG$ "ɡ9ӳ?*_$p.x76ٶy?F r~nIqhh 6^o!]>{:6cEqc"‚& nDY-X+ģk; |9sfË>u)BX &^9sUYO{"xo;,_ &u@yw>9+n{/Lq!o-d;(4Ywdb̸kg_޿?G9.RFY6߰1<\D}peh>;bξ{F.]B.=[n8x `i 'WS+ h[7mT9{30 zl߹Sw/ƕRdeFHtJZv*4k'c}"tZ;l ݴ (B I)|CztzrJbRR0Fa5Mix 3άأT T.#-taU P[~B wCB1 J]O\/~.D{^{E^}{X&4oU`!'Y 9|nRcغf,~'n0jke/.`lUSKܼǍgJjݞ?9ϡ|+5QW semqBeq?ocuF13uR:I~t2V0oFμo rYO_??|[|f I|a4N{ƫ&-TLsxkv1;^|ݜZuzSKfr=_Sƶ+DGX8iԽ]:74ۖiq:+d-'k>Z*n/;;MHLL\VV6NB¡T{q3-uJػէWV3Dh@wXx?xzxxgrݐ90n;kWEF?}|A##mUb8'`yE7,eϦ,[ [`375NjR /|3)âKYc_M1[Yf\\E?y)6*׵0vHoh9N9"?צiZ0;]qg IDATrBUsHfc#qݭS:eoG%'g?>(<zQbjJQ)p Vp.G9܋zYF~ ^֟ zPzDŽXqτ1dy%gpCɜq_)~Mݼ=:0t LNxc'b~[ Mdƭ?9P'šj %.aB@g±=Jx}?TVE;kB/b*ȫP~z7WiI1=1{}tLJJJkzvO`ך?e}Mj<+vB켋tYETTOs'6#A ߒ1a_w&b,t=_2F]W#i8qS]l߲_`u[bl朻1:P0eQ`TY,ump\TGc}eC'hq10=g#ocj\_}?wr] i-;A Pp0 r\yL?9ʪPcˣD֜=~Yּ){5I Kys\Nf[GȨʀkɉx&630d-+t[[Xyqzo| Yn !0Ykv?[ϵ֬xؐqM|rOAv1O>a'L7Y5dٿ/kE_.8,ۚ0\BU}IY'\s> QUF cع'xrFAY[W~ rT&qǒM*;1D瑨jexo7{DgM@Dg;<{ף=1ټ7tp4)6/W(~X.lSGqdX 5vr\,SNV|M<[,Yʿ/L@Lùh$q(TYU2S/Ϋuǘ`I)XMv턆gWeV]}T[G)S\Ͼeg0kf?gS8uUmFq'˵LzP& l>\:,Czh-7HUҘzP]x+>s'#\ьd=]={t5ngK?G-bХ-!Eᮨh? ~Z^zYe⑿@Gа]!0?tMDDFw5ת""bBVmn* r:عu q Xu/8m[p8 :a!!z ' FEEÁAXl.АVV>՚0dew^nwkжIqI ㎛@ۃiz^bNJoV*{!9NrJW`6zu899dddt: W0p{=%1MY'bw؉ܽeϞLt&5Yq4 ՊkLiM~ q9;t0sX-@L4c4t]jraۃ]M!Dfc>$vi[z<_jeo-(hV>*&A֡OBjfӁa4ݲX,bp8WT4J)l6]&?/L2k$kk(ekZTP@rZ:VJ=8T"55: h(z !.HMM'kWr230LM6pBZ L PMkxޛIt\,IiXm7M5 vokגmtNMMӰd`wVBW^ҒbFX I  n4f ^/r Tڰ‚Lw9?ZŰa(+-4VRw=FӿGsb(Doki89|KU 1Oy^ebzV+Mp))-GtBCe_ Uq,v} iJpb%#3~b.#7/ *"dbc;t/%!DêZU*ie*4z vrd떭^Ղ"=5ؘp8)Dg4kdkZr`IBi bpR Sz[Xi ) hq TҲonѐ)(Mx' H M=dBlB⑷싋<|8eˏt3RPYf5QQQGJ>=d@ ^fESX߬ K<9gX {>8 5rtWb+a׫#(#"2"ñH>h8Ț:d= GԲWJQT&45Yɖo+$"ZyP.  {D8)*]usq]^ Z v ߬ c #9;@/3Gp&,n7ZXE'Go=*2o D\hY@?랧kbw$n=eV`+.'&}q!'nJn?~~A7b.=/B) {448> |:uekڝU)gN]pm ~n".R6MGg0&G-tS-:ۭ7q٥d$Cnlya>cB|W˯[{t|x!C׏ocZ{tx+ )3eV806\?ԝ~#t =z࣏>"*DBb7T@0O?X\=ۢOWwz8שkJ?*ٲ9^‹f1+Uay8kJhnғ)sj?7߼3/Ϯ#UȨ17U7xɯr&W3qh׆&a3qveOPZ-Sq#˽r|x-icG%*‚",&N /בcbBS0&Kp]7ve &-+cL(>iχ|exsd:}?wVa UZJG177~TCg$ JI^05_%\`UNSR&L}A^=}U[2{$fGcsPW?J \8a^=)Gl2 [=SIf7o: m4{p']ry,!(F cd<"".%(f甑Iتt`+HYѼ=tMm sNgRl Plgfc0E>4%w+7$]GQ4}jݬ&Z9h;d-’5sw{{ xc77`BOo72kUrW 0(*,6}sj9(_g脮5'?<{GY3ƐPB5֞oJޯI:!V%ξ>PWo#D&NEvvyׄA:گy7&cfOBOџ Vor:ӆG{r`;7\ȍf_.Gpé40_FsJ|ɧoK_%6<@A&11so9gFbN~MGɯbUmv_5Kv] u*ػv`PZ%Q89G0ۗ^»!29-B滽 o6?>cjJGTȨ /? |vMs옳 d%, 4!O3:/l&^W͞b˯n@TwnMм85ŝש.8c +!hj nՂVSEmJehj2XrUn]N 悛Q4+S ‹!CI:̚W?$ 7R7q?ΓS?(୎R{D$w' Br2l#o"֐(N7Y֏w+ջؚ44i!4 MC *v$qDVMHE{C*2l *½I}`.ACT. \5"ؾlعV PQ`J 'ƸDT~FQFCuݩLxD{' !W>HD@v-3zہ( *.2@ʅ #{>gÙpt:JKszJ?42~t_XÂϣœ܇s.EDu7qhh> ъ+{yi6VwM`V&I=g$J!sw~=N b)urQPv(͡2 U \SПxyQ:+s{tW|ɗ1!%7UQ|Ԛbmv =81i @o!o: 5䗻YE5߬|+mN#%6@ 9NLY=\?#:&PR9ٯwh§wF( :j`QIǠ9kQ\9LPdJehϐ-#Pz\xsQYCn0bM%%-tsV=7D_0YEq^?dE<~0BS{jΌ?+wLbUd)3XH],s z'vl87?gr||f$%O%F!ǵXRM8'rű}/%_Iʁӿ %ȏA$?()z嵨:'5_ŠjqeH[2yxF4i8KRj8NgLj'zOJ'D m uEEM3I#W ~AhPVU :X!dȵ\bɚ]% 1 /=6^n$"sWqtFݪH<κ &_~ns%"G<}v7~4'(wZcK̔ӱ5ʯEaJ]O;0c RfLo!.E*26Mf&ԻU8a:ro'GgiGNflݙy͕1f}}υB @,)-=xnR8ɽ8~?[th쫟mٔԺp֖qyW#5}.rS_WA/肉7UzyZcA?klz9r$:>2f_oe_.tRS|e ~&WԿ4e=HAg];Y,XAK ֭7ΡzE|JVxNi=FD6?Hg}5;xc+hwЖQIuLP!&(L܆:M>,d̺tV}\3)ŽCdҕ4j|jGTs}q﬋P=e[r<,nXЛ^Sh)#rY-:^6g ?Eҥ'7gu;tw>c/aD7J55x SLŏfq|WԈ h_>jb ]'@^3CCԈ Gܘ{X _b3GpˍKP6c?|_Ď?Csmœ;r-jKx2+Kk޷Nw;r}VTz.C99\>q}^ i< =6MOב؋"[vIDAT7#&HIKk|k湗(etCCۗAތ>𗸶٧-;E=H:1@3CfqSoͪW;FPP(?:Rt kDSh/_rk9=|U8QXTDE#GPoՕxbg'[7%; x=C[}UfP[SK kvpm"> BenjaminScott"> benscott@nwlink.com"> ]> The &kvpm; Handbook Benjamin Scott

&benscott.email;
&FDLNotice; 2011-12-13 0.02.00 &kvpm; is an application for the &kde; environment that lets a system administrator modify partitions on disk drives or logical volumes created by lvm2. KDE kvpm lvm2 Introduction The KDE Logical Volume and Partition Manager can manipulate logical volumes, physical volumes and volume groups as defined by the Linux Logical Volume Manager, LVM2. It can also write a partition table or modify an existing one to add, delete or resize partitions. This guide assumes a basic knowledge of LVM2 operation and disk partitioning. The following is a good starting point for learning the basics of LVM2: http://tldp.org/HOWTO/LVM-HOWTO/ Currently most LVM functions are implemented by the standard lvm command line programs. KDE dialog boxes are used to determine the parameters to pass the program. If you are already using lvm on the command line the dialogues will probably seem familiar. For example this is the dialog to change a logical volume's attributes: Logical volume change attributes dialog &kvpm; change attributes dialog Getting Started The <guilabel>Storage Devices</guilabel> Tab When first started on a system with no volume groups the screen will look something like this: This is a screenshot of &kvpm; when first started &kvpm; main window Storage Device Tree The devices found are listed in the large tree list to the left. Clicking the &LMB; to select a device brings up additional information on the properties pane to the right. The colored bar across the top of the tab also gives a visual of the partitioning of the highlighted device. Clicking the &RMB; over the name of a device calls the context menu of actions that can be taken on that device. Any action that can be called from the context menu can also be called from the toolbar by selecting the device desired with the &LMB; and then pressing a button on the toolbar Storage Device Bar The device represented by the colored bar across the top is the one currently selected in the device tree, as described above. Each partition is represented by a segment of the bar. Right-clicking on a segment or free space on the device will call a context menu of partitioning operations. The colors indicate either the filesystem type in use on the partition or the type of partition. By default the partition type determines color but it is a user configuration option. Clicking the &RMB; over a device or partition calls the context menu of actions that can be taken on it. A Volume Group Tab Below in an example of a volume group called vg1. If more than one group exists, additional tabs are used, one for each group, with the group name on the tab. This is a screenshot of a typical volume group. a typical group Volume Group Information Panel Across the top of the window, below the toolbar, is an information panel about the volume group as a whole. It shows the size of the group, the amount of space used up and how much is still left. There are also entries for the uuid, clustering status, default allocation policy, metadata areas, whether resizing is allowed and the extent size. The Logical Volume Bar Below the volume group information panel is a graphical representation of the group in the form of a bar divided into segments. There are three logical volumes in this group. Each volume is represented by a bar segment according to its relative size and has a color representing the filesystem, if it has one, or the type of logical volume, depending on the user's configuration preference. The last segment of the bar is green and depicts the free space remaining. Clicking the &RMB; on a segment of the bar calls the context menu of actions that can be taken on the logical volume that was clicked. The menu also has an entry for creating new volumes. Logical Volume Tree The large tree list at center left shows the logical volumes by name, along with some information about them. Selecting a name in the list window shows additional information about that volume in the pane on the right right. Here, the volume lvol1 is selected. Clicking the &RMB; over the name of a volume calls the context menu of actions that can be taken on that logical volume. There is also an entry in the menu for creating new volumes. Any action that can be called from the context menu can also be called from the toolbar by selecting the volume desired with the &LMB; and then pressing a button on the toolbar Because volumes often have an underlying structure, such as segments or mirror legs, the logical volumes are listed in tree form. Snapshots are also grouped under their origin volume. Clicking on an item in the tree will expand or collapse the branch if the item has more items under it. Physical Volume Information The last two panels at the bottom deal with physical volumes. The left tree list shows the physical volumes by name with basic information, while the right panel gives more detailed information. Clicking the &RMB; on the name list brings up a context menu of actions that can be taken on the physical volume. Storage Devices Working With Storage Devices Nearly any action that can be taken on a device is done from the device context menu, discussed next, or the equivalent tool bar. It has menu entries for the creation, deletion, extension and reduction of partitions and a variety of other changes that can be made to a device. There is also a sub-menu for working with or writing filesystems on the device. The Storage Device Context Menu This is a pop-up menu activated with the &RMB;. See The Storage Device Bar and The Storage Device Tree for information on calling this context menu. Partitioning is inherently dangerous. It is highly recommended that all data be backed up before making any changes to a device or its partitions. Create or remove a partition table Writes a new partition table to the device or erases an existing table. Both GUID (GPT) and traditional MS-DOS type tables are supported. Any partitions or data on the device will be permanently lost. Delete disk partition Deletes a partition on the device. Any data on it will be lost permanently. Add disk partition Creates a new partition on the device. Move or resize disk partition Move, grow or shrink an existing partition. Change partition flags Changes flags, such as the boot flag. Extend physical volume to fill device If the device or partition is larger than the physical volume thinks it is, this will extend the volume. Create volume group Create a new volume group using the selected device or partition. Remove from volume group Remove the device or partition from the its volume group. Extend volume group Add the device or partition to an existing volume group. Filesystem Operations On Devices The Filesystem operations sub-menu provides five common menu selections for working with a filesystem. Details are provided under Filesystem Operations. Adding a new partition To add a new partition select Add disk partition from the storage device context menu or the tool bar on the devices tab. This is a screenshot of a dialog for creating a new volume partition. add a disk partition At the top is a graphic that represents the size and position of the new partition relative to the free space it is going into. The blue bar is the partition and the green is the left over space around it. The combo box labeled Select type can be set to Primary, Logical or Extended if the partition table is MS-DOS. If it is a GUID partition table (GPT) only Primary is allowed and the combo box is disabled. The usual limits to MS-DOS partitions, such as only four primary partitions or three primary and one extended partition, also apply. The size of the partition can be set with the upper slider or can be typed directly into the text box above it. A combo box next to the text box changes the input numerical value between megabytes, gigabytes and terabytes. Once the size is set it can be locked before setting the partition's starting position. By default the partition will be as large as the available free space. By default the partition will start at the beginning of available free space on the device. By unchecking Lock partition start the partition can be placed anywhere in the free space. If the partition size is not locked changing the start may make the partition smaller. Conversely, making the partition larger after setting the start may move the start towards zero if the start is not locked. Clicking Reset will make the partition as large as possible. Changing an existing partition To change a partition select Move or resize disk partition from the storage device context menu or the tool bar on the devices tab. This is a screenshot of a dialog for creating a new volume partition. add a disk partition At the top is a graphic that represents the size and position of the partition relative to any free space around it. The blue bar is the partition and the green is the available free space. The text below indicates the partition is going to be moved 512 megabytes towards the start of the disk and will be 512 megabytes larger. The size of the partition can be set with the upper slider or can be typed directly into the text box above it. A combo box next to the text box changes the input numerical value between megabytes, gigabytes and terabytes. Once the size is set it can be locked before setting the partition's starting position. By unchecking Lock partition start the partition can be placed anywhere in the free space. If the partition size is not locked changing the start may make the partition smaller. Conversely, making the partition larger after setting the start may move the start towards zero if the start is not locked. Clicking Reset will reset the partition to its original size and starting point. Not all filesystem types can grow or shrink so some operations may not be available. Volume Groups Working With Volume Groups Most actions that can be taken on a Volume Group are done from the Volume Groups menu discussed next. It has menu entries for the creation, deletion, extension and reduction of Volume Groups and a variety of other changes that can be made. The Volume Groups Menu is located at the top of the main program window. Except for Create volume group all of the menu choices act on the currently selected volume group tab. If the Storage devices tab is selected, or there are no existing volume groups, only the Create volume group choice will be available. The <guimenu>Volume Groups</guimenu> Menu Volume Groups Create volume group Create a new volume group. Volume Groups Delete volume group Delete the volume group. A group must be empty before it may be deleted. Volume Groups Rename volume group Rename the volume group. Volume Groups Remove missing physical volumes Remove any missing physical volumes. This may cause data loss, use with caution Volume Groups Extend volume group Extend the volume group by adding new physical volumes. Volume Groups Reduce volume group Reduce the volume group by removing physical volumes. Volume Groups Split volume group Split the volume group into two groups. Volume Groups Merge volume group Merge the volume group with another. Volume Groups Export volume group Export the volume group. Volume Groups Import volume group Import the volume group. Volume Groups Change volume group attributes Call a dialog that allows many group attributes to be modified. Creating a Volume Group To create a new volume group select Volume GroupsCreate Volume Group. A dialog box will ask which devices to use, its name and other pertinent questions about the new group. I have checked several devices and entered the name MyVolumeGroup. After the dialog's OK button has been pressed notice the new tab titled MyVolumeGroup is now present on the main window. This is a screenshot of &kvpm; creating a new volume group. create a volume group Because &kvpm; uses the lvm2app library to create volume groups, not all options of the command lvcreate are available here yet. Some, such a clustering, need to be selected from the Volume GroupsChange volume group attributes dialog discussed later. Splitting A Volume Group By selecting Volume GroupsSplit Volume Group a volume group can be split into two groups. This is a screenshot of the volume group split dialog. split a volume group The dialog has two views. One view shows which logical volumes will remain in the original group and those being moved to the new group. The other view shows the physical volumes that will be in each group. All volumes will start in the original group. By selecting a volume with the &LMB; and then pressing Add or Remove they may be moved as needed. The new group must be given a name, which can be entered at the top. Any physical volume that has a busy or mounted logical volume on it will be greyed out and moving it will be disabled. Moving a volume between groups may cause other volumes to move as well. When a logical volume is moved it takes all of its physical volumes with it because they cannot be split across groups. That, in turn, may pull other volumes. In the same fashion, one volume that is mounted and can't be moved may cause others to become impossible to move. Remove Missing Physical Volumes By selecting Volume GroupsRemove missing volumes a volume group with missing physical volumes, a failed disk drive for example, can have them permanently removed. This dialog has only two options, either all missing volumes will be removed or only those that were empty and unused. If removing all missing volumes is chosen and some of the missing volumes had logical volumes or thin pools on them then any unmirrored data they held will be permanently deleted. Removing missing volumes can result in data loss Changing a Volume Group By selecting Volume GroupsChange Volume Group Attributes a wide variety of changes can be made to the group. This is a screenshot of the volume group change dialog. change a volume group Most Volume group attributes may be changed here with some restrictions. For example: changes in the extent size depend on the alignment of allocated extents, so some sizes my not be possible. Also, a volume group can not be made unavailable if it contains mounted or otherwise busy volumes. Do not enable cluster support unless there is a working cluster running. Switching off cluster support or using the volume group with it enabled is difficult without a cluster. Logical Volumes Working With Logical Volumes Nearly any action that can be taken on a logical volume is done from the logical volume context menu discussed next or the equivalent tool bar. It has menu entries for the creation, deletion, extension and reduction of logical volumes and a variety of other changes that can be made to a volume. There are also two sub menus, one for changes in mirroring and another for handling or writing filesystems on the volume. The Logical Volume Context Menu This is a pop-up menu activated with the &RMB;. See The Logical Volume Bar and The Logical Volume Tree for information on calling this context menu Create logical volume Creates a new logical volume. Create thin pool Creates a new pool for thin provisioned logical volumes. Create thin volume Creates a new thin provisioned logical volume. Delete logical volume Deletes the selected logical volume, thin pool or thin volume. Rename logical volume Renames the logical volume, thin pool or thin volume. Create snapshot Creates a snapshot volume based on the selected volume. Create thin snapshot Creates a thin provisioned snapshot volume based on the selected volume. Merge snapshot Merges the snapshot with it logical volume origin. Reduce logical volume Reduces the size of the logical volume. Extends logical volume Enlarges the size of the logical volume. Move physical extents Move the logical volume's extents to other physical volumes. Change attributes or tags Changes the logical volume's attributes, such as allocation policy, or tags. Mirror Operations on Volumes The following five menu choices allow converting an existing volume into a mirror, changing a mirror or converting a mirror back into a linear volume. Mirror operations Add mirror legs to volume Add one or more mirror legs to the volume, convert it to a mirror if it isn't already. Mirror operations Change mirror log Change the log type for a non-RAID mirror. Mirror operations Remove mirror legs Remove one or more mirror legs. Mirror operations Repair RAID or mirror Remove or replace missing physical volumes under mirror or RAID. Mirror operations Re-sync RAID or mirror Re-synchronize a RAID or mirror volume's underlying elements. Filesystem Operations on Volumes The Filesystem operations sub-menu provides five common menu selections for working with a filesystem. Details are provided under Filesystem Operations. Creating a Logical Volume If a volume group exists and has free space, a logical volume can be created in it. Select the tab titled with the name of the volume group you wish to work with first. Then a dialog can be called up by right clicking on the long green bar or the (still empty) table below it and selecting Create logical volume. The toolbar also has an equivalent button. I have called the volume VolumeOne and it will take 20 GiB of the available space. This is the volume group A dialog for creating logical volumes Converting Mirrors To create a mirror from a linear volume, bring up the logical volume context menu and select Mirror operationsAdd mirror legs to volume. Then select the physical volumes and other options from the dialog. Mirrors may be the original LVM2 mirrors or RAID. A mirror may be created with multiple legs or as few as two and non-RAID mirror legs can be striped. When the dialog is finished the data on the original linear volume will be copied to the new mirror leg or legs and synchronized. If creating a non-RAID mirror remember to account for the space the mirror log takes when selecting physical volumes. Depending on configuration LVM may require separate physical volumes for logs. A mirror can be returned to a linear volume by removing all but one of its mirror legs. This can be a convenient way to move data on a mounted volume. Simply create a new mirror leg on the destination physical volume and wait for the mirror to sync. When finished, remove the source mirror leg and the remaining leg will have all the original data. Filesystem Operations Working With Filesystems The Filesystem Operations menu is available under the Logical Volume context menu and the Storage Device context menu. Filesystem operations Mount Mount the filesystem under a directory. Filesystem operations Unmount Unmount the filesystem. Filesystem operations Extend filesystem to fill volume If the filesystem is smaller than the volume that contains it, extend it. Filesystem operations Run fsck -fp on filesystem Force fsck to preen a filesystem, use with caution. Filesystem operations Make or remove filesystem Write a filesystem on the volume or write zeros at the beginning to remove the filesystem. Mounting a Filesystem Filesystem Type The main tab of the filesystem mount dialog provides a list of the most common filesystem types that may be selected. The dialog attempts to identify the type and check the correct radio button in advance. If needed the user may select Specify other and write the correct type into the dialog. There is also a place to enter the desired mount point and a Browse button for convenience. It is possible to mount a filesystem at multiple locations. Mounting a filesystem Mounting a filesystem Mount Options The options tab of the filesystem mount dialog provides a list of the most common mount options and a place to write in a comma separated list of any additional options required. If unsure, it is generally safe to leave these at the default. Mounting a filesystem Mounting a filesystem Writing a Filesystem In the following dialog the device /dev/MyVolumeGroup/MyVolume has been selected to write a new filesystem on. The type ext4 has been chosen and the beginning of the volume will be zeroed out to erase the old filesystem. Writing a filesystem Writing a filesystem If ext2, ext3 or ex4 are selected the Standard Ext Options tab will be enabled. Selecting ext4 also enables the tab labeled Additional Ext4 Options. This dialog may also be used to create Linux swap space. Most filesystem types also allow an optional short text label which can be supplied here. It is not necessary to erase the old filesystem but doing so is recommended. If the old filesystem is of a different type, the remaining traces of it can fool &kvpm; and other programs into misidentifying the new filesystem. &kvpm; uses the standard system programs to write filesystems. If a filesystem type is chosen for which the needed program is missing, &kvpm; will generate an error message and otherwise do nothing. See also, Programs. Standard Options For Ext2, Ext3 and Ext4 Writing an ext filesystem Writing a filesystem Ext4 Only Options Writing an ext4 filesystem Writing a filesystem Configuring &kvpm; The Configuration Dialog There are many aspects of &kvpm; that may be configured or customized. To access the configuration dialog select SettingsConfigure &kvpm; The dialog has three pages: General Configures the data to display or hide. Colors Configures the colors used to represent different types of filesystem or types of partitions and volumes. Programs Configures the places to look for support programs. General The General page is the largest of the three pages and has two tabs. The first tab Tree Views is for configuring the tree and table views. The second tab Property Panels deals with the property panel that each tree or table has associated with it. See here for more detail on the layout of the trees and panels. Any changes made to the configuration may be reverted to the default simply by pressing the Defaults button at the bottom of the dialog. Tree Views The columns of each tree my be hidden or shown as desired by clicking on the appropriate check box. For example, to hide the mount point column in the device tree, find the section titled Device Tree and uncheck the check box labeled Mount point. Some information, such as stripe size, is duplicated in the property panel and may be hidden by default in the tree view. The amount of space left on a device or volume may be set to show as a percentage of the the total space, in Mega/Giga/Tera bytes or both. There is also an option to enable a warning icon in the remaining column when the amount of space left drops below a user set level. Property Panels The panels are divided into sections which may be, optionally, shown or hidden. Checking a box enables showing the section while unchecking it will hide the section. Colors The Colors page is for customizing the colors used where devices or volumes are shown graphically. For devices the color can be configured to represent the device, such as one color for logical partitions and another for primary partitions. For volumes the color would be different between RAID and linear for example. The other option is to have the color represent the filesystem type on the device, if there is one. Colors can be changed by clicking on the color picker buttons. Programs The Programs page has two tabs. The first tab sets the search path for needed support programs. The second tab contains a list of the programs that are needed and where, or if, they were found. Search Path Most of the functionality of &kvpm; comes from standard LVM and system programs such as lvcreate and mkfs. The search path is a list of locations in the system where &kvpm; should look to find the needed programs. The standard locations in the list are: /sbin /usr/sbin /bin /usr/bin /usr/local/sbin /usr/local/bin Using the provided list box it is possible to add new locations or to remove unwanted ones. The search is done from top to bottom so moving items up or down in the list will change their search order. The Defaults button will restore the standard list. Program List The second tab has a list of all the needed executables and the location where they were found. If a program could not be found an error message will be displayed. Credits and License &kvpm; Program Copyright 2008, 2009, 2010, 2011 &Benjamin.Scott; &benscott.email; Documentation Copyright 2011 &Benjamin.Scott; &benscott.email; &underFDL; &underGPL; kvpm-0.9.10/docbook/vgchange.png0000644000175000017500000023632712770324126016733 0ustar benscottbenscottPNG  IHDR<sBIT|d pHYs+tEXtDescriptionWindow Class: kvpm+tEXtTitleChange Volume Group Attributes ? kvpmtX IDATxuscK-(R~Uԯbwww&&R(] ,uscNܹwܙssg+=ɘ3[,'  $ULuTzHLITJE9麒7.) KZՒEF9Hjj{H Us}B 1QL}h&TkIYk%]I!XƐwUgv${d3Hw|󻸯I){t$3CI/3.+HN.aVI"?.77(ʘL62$MkL_`crM4^_qk@Z?hq0ڈR=^b<)}%[ӜU&Y:e( zà,VdS,s㇪ a2c&uU9љ3,'^UWKvv;<;/EEvcvϩ_NH!$a #Ek 8%"%:V0Ȏ$.l¤L]nB-{tǬ܌ZID^$?, zx^. ׋?'• CS!c˾tr$NDJt:~'c|x%ɦD$AFq`0~g;wQQQAvv65j eS##]-A4iBNN$ 6 !%%%ڵ:ujt:72gisi40rmIHYju~1aF CR#ꗛC8GFDfT4U6ʸqW(7((׿Dk5ׂ dIW-i6;N?U16"-Sq)JLɐFZ-$,9~}ӨQ#kT-a֩0X'7nL]_{n:?@F~|kq8p&kOZA  BC(dm/*'aO QTRLϿ•N㎣~z), FVXU?z)ٻN: I(R;IfsZ׫(S$V~eo "8IDi4PDz'9R]2sZI{\N Wj":j&;eDdgwLIZ2W$fܩDw/{JJ8ÿZG?4Wc{=Ķmb[gZ!wk_/'j ]}8餓b]z9uulv;9cc Stؖ`(Diy97m۶?z 4 Ҫe+ۍ>^/IVZSP;ml ,&M'vc> j2Y .l7]H )μe7MN B=4+Is*ԣ*ZSc}*^jkY>esR攪!БCԔ(hW+esu8s/HčfhhtI(+μڙ-[wU/ZT-~2fm%JA hެy-)nwv-).-[{nQ-* hѢ%_۲U+v& $lkԏ[nպ5?B]THܕJ$ ZiCA;>%$< ~J'&Mzzuj̤֭NZߪwFZZ)fii)YY5;xܕ}>ÏAFz&\$%ZՑR_dD2L*QMqŕl ɩ]rڀѡ]0{ߘ`L:S#(رcѱ+3_k/SI,蔋D2tii)*XTTDN~wY<*JOf%vffV6R KK.*(ȴ]ZZJ(/b8n(vffVBlS³~nٍؼ=ٚ_m;ٹkTz& o`nrqL_ e!/~wo1s߬s9_<6OX]tPpH8Z8G8XU"깩^_;jTժBݡIZJxĝhP - e/'~%kXwM-cߧY/&ESnaata.7[_/SҔ!kdtj֨$ѷ>RRza'gY6h=F\`׏ڂTVV];u}Q gΜ Ʌݯf~l]x\ٟ:kx{Bo|LTcAο=SxxtЁdvDCCe?qWS\vi٧Ǔ<c_i\Ah/MǓNc+yc~BV8Q.ϼmN$"$eиPxqJ2yM v'4eo.t zL12斅i.:y2 {/о|٭Qg+rSt'7Z[sB Qg94_G0glw/KOO֭h&& yx,S:G>=S&K69H X>_īc旁oT]N9^?nckDQV \ǠSЭE<:~~:څngYEPF^SdmOYwuUmDz?<eܾSsOsfxꀇB!Tc[wTVTXn7O>$/; BSO>=[oɴS~@v/?7p̧NӦM.5}f'}~S_V,,cB6oKI4iԐ6-#~&>|~3((Wy 1 0ңcXz6xA8}x**Gdܙn;v DYOFtZ<ނO}Ȇp0؛\M2i|I,lt7*b#/wYgX L0$ԋKҗm9 aleU5|O?w< qk|<ۆE/gaƣP?>:^&bβ&oI( ZA($m9~iC\܃;=6}J<,zEڴgBAq]Ÿ\pofU|r M JdOkߟKZE%8iz+{g˒*fA9+w1}@= g3e_ ~ *~a+O-Y6&6Zr|;^2=m;ڌ/?dirV}{3#W zƵ9uhޑsyN前Kgٷop~txmi[ŏﭤӃ-_E8u9h7 ,ߕ:ܛY{wןNaDp3 &sx6xP!?:;lv֔ݽyY^f}d=NADWˏ%ؽVd ⇯Gg:;;_ryy aM]!a&;>`(>/=k ~?>T=<Ô3ϰmziC_.c^p>< ˨G3nƛ|2%i眡Cuۗ:ujQRRlnE}~ppD6/7%{)-C{>eEއw_vVM1[7m-fIrXS*fHxS0WZߚyM8گN}rb2ԗ A !)}ىFv )S4E~VvaN\5jm-ܟR6h寝L !8*MtV->YDL>=> ΈyRxԢ5x9vK& OR۳[r؎ԲAQch T^@hMc1O%G !uHB;T>'Y 3~.%bE(ؠlWv_?"O2;rh { ;S/]"i 6W;rY[rdWYoԕrhX#& FN9-/|8s.Gͷ e #q0"lG7gT9[j HW{=nf΂A>"m?0G;p # _'W<:25t_4}pB竆CVzn`~ә,~ 9.; Z|9-r B^WF}{Q! $f>{~ޝ.93yC,-8'U,gRɅFH;Q.z>lb[4X}[X֎lF\IϾU̷xs?*(/r+?h'[:`<HY;jeQ]sC.⒑]@Aivr Ʈø=T&'S4w>xt-^d]]dUFF}^?~fw;?܍c=#Dpb>I|ʈKUW^E͚|dSZM+Hkn^{uthݪ%￟|r;UrD}o1m|8a"}'?ї#I5;rTd۠aF4kؑ:X5=ʃ{ x}J*)..)AzF'PF&iMiZ3‚B32p8Ql]oPީ~RH-+ZOF1ǐsD(*,j) Yw9}OFt@%;RO0EZ½w_Dg~dN咾kMr% (_#GR|>NW, q͚&pnʃer)\wtɣR~'_ϥCx { 6B {@-[ ~vR8 vHBf BB J|~r"#d 1>.6Y" a m E=$k #a x+Jki-)a ^;r(FJTB l.B(j"JP!lPc $ h^|=B Cǧö̝5ٯq'F-2ۍa Ѝ&Ч$\yq|L>%.l o0*8ca|ʁ q -9;N3)DŽ?a9҃almt5u7@($غy#;oEu=lwԏ2VpF8nj"?0u4> rs 9IsQPֱ0%q2\pކɩM]&R?g ̵kaݚu8N3<{ʏngڵԮ][KɎ~Z׷2ϴjBW2T g%4FXpU;=MGe|ܫTXq^!A ;>* V3屬P ֋h?_"~l"+.w (X7o}%G _^|NQ& %oa')*+d'y-U{]gnE;(`'bv(<ҏgP|=9+we9uCyYp1Cn9?gIqwv[ρ@L\9d6MZJQiK'~Ȗ}hY[av+vfjh2J Y64C<1+JqV6nw\dvqN (D*>+,[U픗Ieq _ ;w
[>sϨeT &tg\3 SFać{pW]~K|z)dE*\̈́OSTVoL`k<d᳟ێ" >Ȓ2pW-L7zT)-| "}2{1RoXv2vU ;{'oO3}2~2VO5X3?{㮬?IuI*lԩ]֪}}{ٵc+SO}Skv:AIZ?ylm7᯵q~\ic8B /H IDATc{˯hִY {#&arr]O"$== t222)۵1Won#n]23XrLqL~_5ɩyƨRUHOUVi'3V+>τK۽vsrU7aw 3hWMTIQ>f^zq[pɯKu }Πw)>#%vlS[ր<@E3dv0=D'[B#3-u+Q]ai,r3l#{tKޢpP=@PS|l7:ұ\PaeȳpₛwZ_n[t2?7v\ugO6=2N; M)%K^SvfKxz|ɻzzO%JDEBD(Ds^d͂7.ovu5=SguPW}nVKx\(W T8[xaV,xcYQ.ބAW/RG}į71a&~)ODV +%eXqv xjN?:ާc;wA,4}#z "6)#g3v] ̽gSbe.D=[~b%G,n|2*q-GjYJNNNYXZoን+ɬENvQخLVR}{Ky\ +Vv2v\Y1?xt}5ȩv# =\;;m;'^w9^t#d*TYQ!J><t^!N8)q9sKvx(eæm*CN'iX!a]cded>~PNZZ4֬YC4n$Fxd,$_<~>/ƝZ^9+!2lq$VQ $5s2;OR:5~!aϨIvV|Njr.dP%]'\g3\UtÉ؜g! <wi$F "\dK##3]®t df6gi}~$ oydH_Q)ddWu:HKw@{=9`n<~#Ɏ+I# 4W CHÕ@B !x 5SΪ-ˆÕWaK$GN @urpFl8ӝװoM;q:]"Bp*l8~!?`m\Ɏ cpr% gz:xgYӧUG>G#9qus(=É2%;i++㣴){3 |F{Wv;F3Ksj X=k"܆^&BA^ L#x**":I$-}:lidi}>p,O5j,#i.2kdfA/2df+CrtN*"5礑]+x ÌDRѕ!:: `0Ȯ|VZEFz:m۞h׮ бc7nbJ"vz*23iz}~:t@ƍAu8իV'Y}4n8Ώm#f Өq:{M kG4*& rLN.f^BWBhP7MvK ;֯bhѬiC"3?6p`~6kJnn.^WBnAN:ns<oeac)Kb,NS >)#2 7WU,w;`kp7#[ϊΚȈY+T$HY_pՓDjQIƜ5ާWGONgÑ`Ա:~ K6'@: n`xƤ뎙!̄~EYw'r\zHLJDG#c`Uc6_h84BINN]ڴ9c=6#;ٰa=YӦ4;vS7Fd'~?;wqz?ޤ忋h֬{ԭSmpl# |8 WȒG;q-ğJ{?W^vi.2{` `߾޽B iа!5nݺ8TIQ퉝OB 1'Ȯ[S~j>]^G56+ cU%?-Z&A )K\PnWz,ZdՊDlAX$C,0a StWv ^>PzVHlr\Ft dL@k. )+-EDa͚6gݺ8ƷmtþvvSTX$J$Aa.5VDd'bػ).q㹹 iԨuIǵmۻ(*,<&a.i2ቤmYU^sn mLjs~?x<^Ntp:I9 /z0)g=Ǻa$bVDYb6 L+)!CkJ)#,$#DFJ(E%fY9"$Gqz$9媏(ϔq8ddD#^d4mŰ=_k許 t8HOO'++3I?^+1J<3,c'MxNIW)aK|Ug'DG_FUr PSY; "=b%QT URY$61d3~hN ѱRodO(HqcN`"z|)$"M/2%WTn֔J%m~19KU~и^1F*YJ2(*0Uh _Sm-ٟ(ۇ.w8:>%:jCk8jġ$qtŪ`+Ȝ*tpD0Lq☙ZΘ)ݣXv&=IHƎĬz]O:*d)dj$.=:CbHCl0h 1L[&!oզDB'f:*kS ̫[XG2'1h{bc"gPEnb$R.$Q!OhI.ڣ`J %k6I&');簐)~!7Wѭ?&ILuGtD欉VքEVVRмUb jl,Iґ5_k6IˆG!1fIJU=']E$ }SCFDBë(ѕtXDea$:)j%QCx@ѠmH5yIDΓ_ɐ']eDsn%FАmJO4hB-FLO("n$ @~kѹͧ- Fj"zZvHgT͸n*!93T NnbBֺ3cj1)1%9)dȋF 3bYX%[-UЭJ:|'" ?SU c.fz:te㞤ѭ&YԑM@~t7JTV晀nN6h'MHZ=<,' XFJS!4I%CV%ZNa~ci"gk$"l(#> 8ʕL'N?ch.DF-s{,*$@qh7 $)46Gv4[ ADHtt6G}#Į4ʥR[l:)mbA؎+Ai-E:n]X5C݆2405mqBr79TP?ۑhG-kHtɏ$ұ3*$&@J 0$|tHIaNtT:'V6&3}V6DdBXZ{x dɐ6z mUHrTU!:-QtH (Q|%G|(7:1ЈT3Do MqymIt雪[BNη . x*[rcUE+oNrR7VMq%eD'"ILA#A~/UCR79b G 0OiD:aQ]"7Hiz nn2U LL5^ʘԘW J,dB-#1UeZh&9jUU!KG)Q(ST@TG'iRPǻH Z4&X 5 y eш%IT*A-Ɍ\+qIQ%Д$EƼȜEe´}$c7JلX)HD'rIjG0QXe< K&2[=5v ]"ĤEwJTulU 3ɐ Km>-3ũk©XvrDڏ% N*f\5%*\eŒ;DHRL!)-JEk@2.KSBE3!Cdn$}px™D(NL.I iHH4F_zbJBK򉑎5 ) R:Q1 Hb %*Y\fl9zɁ@n"f }ȏLHzPŚQe8"`NGoL4SEYڍ7m҃ +z)g@ K(· FbS#4zq}i e ?Q]H?jDշǩ,- >^FJȘ"y)@8VގhHEH&i'( z,Tgc5L%IiarqXM5WmI%CK+;l"n>>V҉g)٠`$Ua3ӯd5[+ b'N5?yChPG趽0ITCKp5KxRN+)[I`#x#3A K~;}M{ XTD^2MYhE$[ȃ.UDt6gh@R*┈Ufnd[|!M%bߢqLWיֈ;XJJE&n)n؁hV1aAX(Yw>-B شM+J8tOһ9bSMLHTrDwܟ82T !cP$ȳa↷wȎNvi:[lb^XMSefC$uHDe4&2BuXmĪZ<$ѝj<$LwYl9c QfR/$@uvhg ƿ˶3S'R)}@3i]gB !206juL@7z+o#~yӻ3j(yzӫ0n|Gլ*gCѻWoz=+}Y݄WfǬ Bz׻x+wr#j۱$EG.'x3ΣwXTZS_ΡO}qc՝p6[ d X&_s8S<:$(SglN ̰Vp^y顾3[fΚć~@bO>_{?   Ak+2McLzx/ m=w|8}{kNϡ8k42̜}db}sywN1Mq9oC IWU4<ϘÜ&- P9,MϫQ8yǶN98.:}{?_zomXd3pGS|;{߽x.ṟeB% IDATn4|[>;&~ܩos l1TWc),?Qenzv%MG7 ^фߟWE6jD!3bf=k\[2Ǐw9nov~17 %''n_7~ 02NTNs{c\z Br&~E iK!+{ ,a?m]ϟeuVt׼Lk#%IjVi8m`/Y APۗ~c c䣟R9کqu#˼x&\q;o.p-Lnf09:GPz ׽EnE?4|iJzyi ~޼? VMbjW}Kp\T$(SEɯȦI5|Nַp:2m7>f3D<9MnҞidc b#nq;D2m7;9f3 @`_)>=d9qfCsλ<@ڨǸoKj\:~0~2~=Ar8cԙ|450Xäi@H{yQ䵡^Gz}]hbFN|x (v;7 8Lt[Rɓjw;78z2sr-Ιv~~=i?΍}[R'E6p'' 0ڟtdß5?wqW|O#ԓ.7sUNS8` '-cLdرgԣMkx,ǹh=ŎNp9&ˁ3:^p^\.堈w+Ff`m`du?Aul-cwcG\y?#@cY\Ͱy̜;YS?敛S3Io$~v8Mȧ ƴMYi? 3KY~s~sAk;ws0DlIhk}?Of镹 I lј"M80Tڀ`%iT 35*BTdHۓ9X10-ƕ\Y1% PH~)XΞrS'$Yfuaٞ,&+mjB2AѦ`{+Ѫ9R42)P@}W8뷢IjS#B*j©h.mv.³1ȵG ZO?>@2榤,vʊ(?(-L=ːxP:b)e_{ LL1Dzs;5NMdR+;d{(PR{ $'v?>R72XZDYjoҩ[-w9! mTbZLr)iOi1Kz8Js250*FTC eiTꑕjYՑaX3s m;jH[{siwx`2&/*K*eh+jw)Ε,= v-C†[&9Y=lw-gZM 3oܔzBEgN.M}Yϱ YcbxFg+KSk3yc(IRF Yϭ[* 8PqoE A6M-]ʸ]i4I[ ^g%sw[2[P1z3}ٲ G>f*ʬ j\jBi_ũU'Ů.kb;kqb&{3- r>;o l4jSY΀Tq!sDs+Vs={>[>֢(nĪF./S̾ſrjy9gHwRKJ- ? +R\^=3mqJ򻋕ji2|lWy~302Z5&g9ݱc%{rcTAEIt֥91,aWޟծvlokNQ.3$'8KV:±H)ȣ,d]VӬIF((چn_~8觲~(,j} IIG.-_MRby#0vC3C}uq;ʳb)aLy'NT&tz @7@0>7%;31lpdZ~u9֘i{8p͏k؝K͆ZL[kj1$зH㳗s?RY?:^AQ.bXs8y~q.7-s^;jߑ+wu?]Cy)؁#Xb ye!m$_L~uokq#brhvԄaw ^ODZJJ{2o3Md !kdtoB5Q@Kcm j~/!jv:ZAB9>8oi'rm$ám+z-`cjMtG[K16ք{z sLDB13^S. T 4_OgpP}|y0fc[2oq-+Uvg\ft`h3[|˻) x /X⺪̃8;+9tyTl 6.Ά=_2/ =w C"Ç#|8c.8*_W@!NM`,5^^GMY0^>@ЇҢcn ªUj R4a{1&ƼJ$EVO[ydwqH] |%݂.RxHzV.*79HyN-1īfۇQCPr+{Y3>Iy5K9)ot5Jok\mUL)6Ӊ)߿>gLh Nԕ*z@^#%)MԹ+^[A̍T?¡ rq 2j(K:qUfv#5Yy.dČE3!R~ՊcN"6MI˫5 =nbt ?тp4.KlcI A8-3qʔZI.2GAYϬ`|hJq0nq.Q/幯Hުɇ'9&5ctz^= p4nyFڒSbϿ_HcҊZjr~ОQ!!\Q)-qzk2iQtkٙy} Էt[(hYx;R؁Q-_^}{1c^FCn@C dęLq#/$u:xHպz>}0}%n'O;*.s) bZrY<̬ߏQ ɵdWIl?sUϲ R\;{Jke^Y6K$"g?-4K(7@rd.=o!Xɸvupw|r[n$ý/X)({۹2ɣʯXC;~}7nM1i\E}I0;^{*۹>ʦM<64|?p>+9Q>Z~)oIeW$s۸PG!d겑#=N)叱p|-V'l&|+K d5ٶ5ӹō$*_,ZƲE >^'] [ wlO_7ks;c G0"u},~i:&;?o| ?|sRΖ͟Ӟ`}'~[+<}1/ǣŦc5-:9i2Rt_ɹ4~zQy?۲e,}  kȏwXt &r).V}+Kgep|Gu8͘Γo,!:miq~8=cRnL|w",W/2Pc,)Kx-73%dث6P><s Wb Vx&?}ig?xx^.Ouއ]@^Nn}|t:^MP|Rdw|[|Ww0*^M ʶ,V_E0~`F-Zcm.gdGcШp~܊a=ʒ_>q _J@ uw(:?t&&@. c奫|<*(>Ζ2G5R|}V5a/@[|!G&+ϟ iNKWKɩ~Jkg#}KLۛF~AuOOkEz xa,QF?:r7~1{"}[JV>w7#^8Pc̽#ܤ?|OeqM==3MzBڌ?ȬNMeͬ)%-Gr#C+(/ʛDE<oo?X?Mu݇gω,>)WX~{&'rNj*^1JųϿ@۶mV^\{e` 4ՕfjeYm%GX{4BQW[KXD[ǡ-QɣI%eP YlϨA+{z|5_N "sģC'[2rH~ҩ0<ܝb:Y&4V;\KH-.{xْ^W)>CxPS{) IDAT~~}c27@rr2[3ȷsŐt%WEP5@eox)7ʫSu-i[t À5_KF^1iTz%3\#hN^o*_ϽGxrз]|^u߇g>'m/,Bȗ*(\rh.Yݻ8a&Lgns=ү_?Jyu[ :&GP51D1;00-R]XK갞9m>X~ g7eb<ʓgd,nw5DLM 54JRyف:RZʆl6?22 W&%wǀLN"ȹ{K9? ŕ<ּ:K< u{7x\8LCЧ̙MsP~'ܿz;U~n^XK*bx>~nPu]7%&zG*_6L^r& $Ƈfu~sG!P~``yfvͤZ9nBZz,yfӰ%.S,E77ǼSV6L sk4{zc?Ĝ:p<9Zaƽ{a=q^IW`,fr7Зݤ켫qQ0j*_y;-uǵDhX{9QQ>Dڟp}}$?||k{)z Dl㊯:C^7< qf:winC󈌌jO>\[nz?V-~"[wtMgl/^n4՗b:g4ɖE|'lAr&BfT?-jwq qCV6}injTw|4}+w &M7'_͋tk_-ԎŤ.9_mKx :C>eTmp:R/(Wk[+1fċ~34O,G,}!}LWNۡ>k[m%_!S3FTkdddb4AQl;} ΜSThjDZZj?d)kdHmӋ"j5}4a'zpЊ8{⩷q }qghȋX&bG]@DEpza>z0q~_|49߾#z_%\7n\7_Z^ sY˞Jm6JrꛟHMs| !/RNa-ؖ_t h}9%Xͩly^ޒX#>PqZ˓^fCpnYUw_Om3.,^|͠f0c'{iڒk@!\p%~7Af ka)|:wA7 `X0/U#le^tta H͙Pԥ}`uou{˧~9i sNi9^"TFVȆu׿F?E(dǜW61(o0P Z|tGo4n7M8}ٿpk3TA=&CxI\t.65ư=В0Z{|]MhK'usǛ8a"n/ׁ ]Lճ;g_WR8au`<\ԩÉ*PR=Om}7~ɗ?Bqݴ3* cOq׸oKOc#1̺k/Њo8mWTDZ,5b雼Lϲ hFKo^+uCr=?F!:o>M.r^f7rÌ J5Mh3h, c8_9ۯ%cƟ\EOs _O>>>{_6.>wNݟB.~T(imubknF\r)e|vCiI)vHLt|SSزe3֭ sO;ŗ\x(9˙py=3 !ZMdt,?~-m$ꜗF!"*.ZyB?~a!!Ьi38*8#ٱcߐ.r;Bqv) xxUׯyF JM& Fb(4{>m7`@rQ~uړy2a n'XB_QyT,Fij5& 򴗫*+wO }x|H^/BNsU*秞 !B4FKK!T x8(B!9h.B$B!O!B.v;6 1NCѠVK\&Bk- TDb2y9Lzj*6^J !hjb:-Ed{\G7мEKvډJB=% !?N 4m3?Z"''(.*ȫl?G=Nh^ɄᏳmNϯ5C:+pW\|MXB!Nw hFldggs^` 9'|&dgg$$ R GR!NFinQ\\fCQ_"5n/[nCS1塷e6VNV92dÑB5d_TCFfǏ f}7rmʻwL*le˯?$O9nd=imzboå;ȶx7;4Jx+ڟ|LSIU(=O|ϫwY Ƹ {7 N䪝Ϩ9-w;ϙ÷+p0} z,Wu>##Nn>,'9<;H֣?]3BBg]4~~qޛJP6s)[V/냼n bdst?M_-#t^ԧ/vxN׸\8eH9i/M .}jK!'WޤgU5Dp療Jup_,Qhas^^0ܮpS=rV̀%#/JZ^*-zRHЂRx%ss0V3yu Z^: &_ܻ,0{AV߽Ѩ+QaJEdj"ڹrcG(CokTomtxREE|DO(;E /b.+'xQۆǛt`" k)cTQ3ė6 !8SPI]ZLʢ_9\~8iݣ,%; NӫEf9!v'X%S_$mCY>iӕ/MŤѪmZC]%mno볱UKg#{rc7})kG֐Sc4FsoGh"`5yu{ b錱ĭWLي՜^\u[G! Egdڰf{|| GD7<=)'slbfOy i2XV<͠f0c})mhVq/\ˋe/kٛQ@FIV}3Ѕ" U3)q=k[jM}m/ǛtPzГc30gZg9LJziuחB!N:m< 1mRrj֯]G0PTvT*֯[Kh*z* l wiubO3SqR45xOFGKsP?ȃfmTUG0xdΛo&Ǫ'`nx5:EUKzg*c_Hm1f85W9m0'~CW>Fq>N(t,GMgxo?u3qxr‹7~]MvkWrlc1Lyz4-s<>*>З^vE}m/Ǜt6&N)yԼ?8qQBGߥ JFZb{nFC#5g,ԉheFz=[&0 ug_L+|!6BFho&)) vh4DD74!& >|˗q`bbchڴ&Wk !BxhZIHFh =_a2 `0嶗zU˗y]_!B9tZdh4<*Jr d cK'?M{*nUTh4xוB!j2=B!x ,-o$BsLQdG!g xBqfS$B!Y-v;6 1[NFu!Ba*WE=ZBiyx, *xLFIMMfkZi!BqAՊf'u ^oEVڹJVۨB!AҪM; ).X tqMٷ{'aaab'\E! w8 kGOZI] EtzKb5_sF(tq6Y!Dkf}>ӹ"}!!!ghʃ0򿂙qM^+͂y;·1'09]!?3mv(CRmv4jGYUhᴺ gd{hPkG1gQ3k|4^Q^ ͌a_sM̀8ߘG1U.1_k<$GgMe[ade2l37-ޱ=Ɠw~ISl,c.3r6ٳ|g|KJ{ynbgyV <|Bw! og3xr; a7>gF&k,ؼͫBqvj n*elEٻ˯ sXBnx D'8i8'˶r!ےv[xoAVk62V!(v}`sf*ϫ0'~G5aIwK9l~]%ܨӃkHYrn\{,[ơ<;&Fu )Kƥ+<־+ѝ K1`+!C!YK*߮Hby4wiSRr|w4ʼTZ$*o$XjuoE.d/q8':4dU+CEr^ -t~ŜKaz|=, VhaPwYts#ugݡ =l;T&o(?]]}=Uz6!вCg̴MSPuūf+Љg>VvCQZpi04LVv oPGݱ, $*PM:x.L$LzDڶ@~3Ok+[ pYπVvR@OuUZ?n^J* (>| `)!@u &}Ulg@vi#ԔM^B, 4Ɵ׊ץ* ~Q6 }HvL|5FDfsW2^ByB*#^vmȟ<-L^ܭ[[f,΍o^GrTպ֜Ce ^`yB$[~&6r> !8㝂kxJhb_M@L͢A> "& Gc/E%4d|U5h6J#+Jj1Ť,^đ6蝔ǜpd"W}!hZ!3#ɿr.oɟTԮ_s.?=!u$=ۋwYdZu?֓3.Exip{3 ((9o!3XvCPZzs&%vu-(Yž46}2ӷ PFD[V467iyW}!,4NV dڵjQ@T5׮%0 OMW|8F1gy ñ.|q#0dun={`Xp^'b<曖MuԹB!oh47廯")5:rWdtL]l2HhÇez\'886md^!•F xj5!!! (,,d$((PAyUKz]_!B9Zdh4b+JJm dBQSrJBE #B2B!tZU*QBq6sx\D IDATUC( be!BF&B!xj9$B3کxZnfazLhH !B2 8\\bAW IK=b״B!8gUY'{]eZmJJ\4iѪv@e?aS!9mj 7')(ÜUyyųgNӐ+6'N@FQ7lj' Aqwc>5̈́JsR K8g7[_<y хѣ_\2S495=Hw "z _C\!FC49"NQQ1v)ݮow~V{p>$b Z9 wT*4:#FBi b/f 1H!8]i WԖw Y{B^ě<-Cm$m[(g)[@L\NtH.GUwFu_vH'ߢ6I EWyt2s)ݻ=G)kQ6GvogJ5F&f)e]~,//C֭l0mSnc]*EX:};8"ea ]p՛H^qxޛ;SQVɢe(\Y㶱Qm_5~4K:1FPb{8vZOH\l_i=6zbb9ʞ'(k0i%B4xr9639ZZҵb͆tν bHs,a hIҫ(9̎Vݗ^Qyׁ>kjj`{зhOsBYr9}kHM!*e6@"^$ ؕSTā%TTmWRcAw8-Wֹ> jK>{1"gcՔh)I[>}ף 9ql۶(;$t_կ|ݲud;zo"N`ӓD>BqjoǗs%KulRB殃[w㜸  ~!{((Z@NP'%ĨCb McHP`!/siT=E}@:&ajCI8 Gȳ5@;\iK0p<8* 8zFs(:PRdalh(@@o1Sb=iJ`Z5jtig mOm̝ۧ)(ΧSB&DXB':)t'W* NtJ?aѣ;2B4}ৢ?)rxhOi`dWYXxY/^D H/sC~ڠHEm0* hH @T. S1l */Rn@|ޣ)(ux_`V(ըD-,9{RH/Q-Gwp3W?/ V'Wmt]ԖosHLF{ ᱘dbcp}lkot_T;Wޖ_[8&5@y(+wB[ (┖EpZBAGyt>oK/n8#cZQ|b+?X7K\4jTTV/?P{iPMe 1d O4whMY$6;HQX;7GB{pua/ɧDl6Jm.W hl%XmQ9dۯ-VN)K3eoB y˗.W_1oZ 2酴 <(j-~:M3#bNRr-YZnMJl֛^ ?BGPab9#?*TbY{cG)!@Su`VaЪ:S%N}P|<"S8nṮBu3<\kj#!~Ed[2QpPlM3;)L;BIHm^!8-QddSBj1kbcd`+(v+%lRV̂ DIk-ێئ`ymp6KDLP{w&ĆRHV&r_ ]!EՃlG}ڦ i, RL0/C8+> 5y,W\őkY_Ov)7lb'8} +V@߮;]BMEļc;Qbcwl'v*d~Di{ײP6V;vkهek &|uq &2lЉblkQֳ%?ǓLǷ`6;vK!7vm"} 55lOht2ٱ}5[ ,( 'M/L!ȏP՘L&FWw_T*T*_3vtmw+RFEQBQgZh3ZyT'BGCR8!B46my1!B7~r!B_ySZB!8I#B3rC!B4F y4<$l6,V멮h :FSQq%, Z ͛c2RXhȡX,yǷQ ٕdI@H!-PB;z=t{ p!PBI!={w?$۲-+[d;3jvvv`nun x``nTIbq,í L3nB\ Go#8[Z8e;}݆~$'J`7A x㡱8ZPU1[ۉOLĄt:p$v*O/ hCQTUXW~gEc=[pGOr@/v6}Æ(mxN,`]\1 e@QWY;~v; St j\tNo[J늪8ag7,[DAW0Zm~+7jlwG\w 6܊^%coLv)n׽qz䰳2+j/.x3>w?bgn%zq/[0eQUXі>7{|RvʾxN=}gd9vA a\мm~8gM"[yw|.d֘I巯+7z qڜL:8V7ϪM95N;TYb]_yem՘ʸNEQ⺓b >{\wB@ANm4=}%?Q2}?@|5>vhtɘt0\p&jOWٝen7rxn[;Z+2v΂N?Kd݇GEQhYܽykfD#[{藸~Fzj'v;3oFl}oᦢ tde_3<`O +fbHcqyO4jr Sヷ΅߯;l_ϾʣWl"}7yy3c&sxy:4cc@׫<}!x޻;?kL|]1crWW8ֿ 5ݕU-y+x(ˬOcUSxuS8~W'Pw-+_KGjљSs}q:_=:/Wp" t^AuDgWJ닷锫qu3+?lw}o<ŵO!ݬ':c2]~6i?[2ࢋ.b.8CbG  ;p0rl_G9awF'~Y6Wz;4<:o$)fzs2?J׾^[H2%{\ξBFFOg3K DN%/l!\x17uf'W̠/)wb;{%eS.c&c㭼/!Ⱦ}M"yIL{w87r1{ՙEAk7cfO8~,`I]ȤNKfOGr̔n|LE̛ZT$2MY4:W,[+93{_ޗ5Օi' W^}5v믻ƆFTEoå]#MW$ ^ |TyXTS[I:Pr s}O݈-̎cg+wj2r:RGz9AuD9^Ql%|b.88TW}]ZE.>{5Y"` >1'r(;6u]᫴5b$R )F+5o5)Bwq7[cZuj+}Pk陇ZG1PCԥ;}8iK/H߹8l̬_-Uhkj2 3/*L1G rgueNW/ZNű/XMŷ_X4|ctb7) ﻬoa)8`4IjTL%WȉL*M{ŏU0/I{>Ge,}?llTd:Z[+x9hmm 7rw0C>i-~:A~slOsSp('1T={KF͍֯_gpI2 űv}G츝*?~kL#دW[۪㽇B'*ITjm-`Jii_kZܸjXnȂ3'A-vUxǥ̲7^=9qVrlJ\`r)gO/]?Dk9Ùwi\t~˧ %75 $Fo}+?y_q5S.NkHOMx@0Fx@}Q`xIg YGNf&ϩ#c.K_WoZP?y{yܴ$7{[κrة0֬W]COp`'}O኿gz*3q^I=NۨS8ҳ[8W>&Oi;3/})sudxD}}Mnk⌻nb7>ʴ6= xA0/BcR*/u^qlٺP K m9{"kGoa;5/_\.6m̱ Nx+"ҮN#y1Gc0[%@atz G^fT( df.X ?/q`G BLW(K ~3{nUܰn֠#u\u͞2pTdYDâ rOHY{Iϝ1ٙɽn-D?XY#6&JBR #1+bubVP  z^PIdĤD^de(`礢_W^'N 8G"h;~g0Gü.eqF-`wagxVq`m廘m7.cr*0R޺YԿ]Ϯ@Uv.vQ[_{vWGR:d w'zJ[]9Ӊ@ oZ_~ADnzNf!iч۫2hy ˭^TU m y0i;mՃE4+kYpt~+k_'icjUn?CL#Uw9g;yރvї-  jO/i 3{ 4 Jn+ܟdWg/.'洞d+\]aϣXo2r,_qKҙoP"@+V$)j^NOtjcsWro)<|xl |&r  K?],1ssc# /~3% Q>,Λ?8tzoyPu#,{V`"c=7cDPQs*O޵={Z}s}Q3<HDVVVkt:233;΅ҚE_JӞ3 }}ݔ2Usejl/ ?~ 6ǟr }@+҃muf)}?y73FoyGWpփesr *7%F*]ͣIKjF xi=˗\FAD,ܣk=}³ru&P(^.&_TDwf?;[޾V!U,3t]4ÿ}oߓ;iOvYQ%%%\~?~^U/0| WHBTErԷ8\pמ:QfŗM'_QlRVdq܉2:5N1.3.f/.*?_r-1BY46R{pS<\|87m,&=kt-455*MMM,^|;K/G٥ŭ]?Yr;u9݃+|<E_@ i@UȍOޡaѲDs8xý9',4?(o Sg>~84!]3M#*HES![zS|CK%/XЧ2&~;_Nݟ`<4;b4ZbsKӪWdg8)6(JDds%\qezm~b/ʪ*׿tbKZ\ jTB vq04_Мba{+IU#A\i$3 Ic޻I7.-G DT~s~jd;[6n.dUshпSd9-E˩bR>G@,=X꿼˂e3;@!À!eMꫜ{"9d.-M=Jj纕>tQ?!g_v zyx9`7GLYk8m!HwT´z.g9U(VMg'wp4B]UB'yp<~Μί_{H*'VzAGN"}$fƫVݾI~Ϳ|_bx$UZZʢo]~5><~t,ZV#-vZ8͡.`;deXRvPYUO?~(d KrE- Q[$ {" DNv6|CZ[[[A%..41LA`HCR/DIr+w'lYkxS(dY&>>Tl6p4fbcc1F};E- PQQH+L㡲,*7aD&cT.? =j]B@ D [gZ O(DE "j2 N Z!a'RUÁHHacD@ 2$IBQG Td_T)jEvƅ^o@Ӆq@ :}Ї #h>,k. HވDGByi).0 ]oCUZ: x9АvA'3zXVj~Xv 0K(>?>N 4cRU)BkK X-X-X-Ң1/?噻OēN窻_p(*Xdu"Bj?Xs[D'vf~ I޽"CsKqqTUʂ6k+44qpGsO;/-$~o|23g0IB,tt mhmK?ĭ}3#:tBq-=`[,x<*6UQP5$Ql6Jo f&κrf';*G"I*>;Cy:??{JsP5GމR>p>N;! чO7HGB٪jHZemVb:6f%=L.`ɉpͧVߝ穗>  8B̽5ǫQAsMvQ-{K~+i)|ҙ<3(,sw=F7[htI?'}x5os#N.4:Kh:.b¡m(v>{2ˣGEXwB_ \yy;[>{pn{+rC0ܫ1# ˝_O|7oBIAgmK7uM(//D2 ~\T(_04ӏ`=￈-_p/7,"Ʋ=}/dA#K2^?M2 lm}F|-opÿ2[xzzR<}<|jzI,qV Dߟ+g΢8o=N1ce$SIk:Ax۵x:(!r$[FF'srV%ϙi~#"v5CI5(C|tDO֬Iϔ9f|зM#Y|=:Wn7_Cwݩ: P޳>Hs{#yYr.dl#7Z~fYn~q@UGV&j\fVu1z3y8UPU Za0!'Xٲ[/O6Y6& OI%F瓥hh,aOؔ 6P4T],Ul$ȈNlŮt!CU^ŐP̎߱+Ҙs^X}LQZwpNoB7O!u\Z 0 TT8CߐPGΩ٫k8!7DW ɺGպdy<:ivMOj%I` t[qb> *HLwΡ)AQ> ! d&࠹- k#8LHTjvc>N:lQHATU qDa SOͦ×1W:U~Կ2*Av&?u^N9;G"C`dqQ5E!w٦?u9@?g _z9iIsh %h+k4GQy]÷ު/K~X g600}D#_\57U?T.n$ɔ:[ӈ5Ƶ?P1(P { '%_XUN?|C㈽1l>qM.coXI=O<0ԡִ-c n5) gGlh72! ZlF}QKZ:5pұh9մdDmM5q !9juRyG.^^n#խv܎7\ɭE Aoc>GT+_ŁudCǞ @<jm.ܶZ6|,O'1C ϯLݍH܃Om{ r{?`w7P+,yiO#QVAglSv [V!*I^ >ͅ1ADͶH]<ژ8uf2r8Pk{0$RS N ypT,q;˹|o /jIUpp:l}2[.Z2111OxA%&bmi3]l|zW~ClL b,?-{/ D%R0~:h06I̾2*x[=hf- Yv'tbțύכyi鿹z\DF>Ύc+7rq,[ox;zfq'1.؂;&n9ܾM{.|{,8Jw/eCWt)&=9~%ߢP3ܸt;͞(R 7^ܜ^C(73[u|F):1s)SK9!\+Tf;e~C0!&¶j"M}QSZ:uiH%۷;vٺ};9rWM"(*9uO32o_E'K(, &&fT>UO'q}'7ilAi. nf{'Gfogɲl>#F΁8o#M#u_}2tz=9y틖{N_d"7;J>g&..40L;E9{ŶQ˔$ȭמYEW#fұKְ?i&!ㄿAJ; Z*U IDATwzHL\|e(J?G뫢$!E'ee99|GZaq}W GZo )qhĎI"IXv -t_~:ZO.k߾H vBY OX<;ɢe@ dp- P -k.V |q03@>N | ƇT@ "HH@ ؉g ' >@^_Ђ(xˍ*HޠG'Ȳ|CO%' -zJ="-X|G[_7l١yi$X[Eۏ`[ZzrXmL< .vboǘ Xf q1}~^%JzG-v3SO %~8 k\åG$ˌv%Bi G&ymdcS0>FbkvL{'Iw[S}%W#bA: *q㱵f*jiضi# NJR 5V uZY>=COM+P#KASĤiI0Jg׻'tj)uJ6jU~CgN"oT^Ѿt?ʤ`3}w vcw: Qz\WGg7`w:1L+aJĴzX ~]M̊SOJk(RrFk<2[Jss1ʈFi`{i5 mnG;]= %fxFA@zNyI&OQAʼn"Mad59r4R^ZAu:SE)6K)RĦ3 sGUq6&\ʨjlVҳrJ2vv먋=z֗U=mT(KaK! h]ϺjA"3;ulhG_8MlZ uP^KSUUY`->lBbByy͝ji%=#RF#R͈$@6$_ƎZf%[M%$TȖ 2#?-l,1LjxtGc1H5=u[H($پyۢ&2:{!UUq7`6#R`F[s%[m5jծتsc-"'r+8V5lX_QȄL:jI$cNtt(iHgBuGC 5rd<ŚʩՎ]NˈJ.dhtʷleclG5|(An|6UD3%7X6:޲ژFOMAJ,#H p{VU]m/FNcrA,Q8h+gM(ƑezؼO1I8E8HBz)/uIL6?u<}כr)L7c$&74տv_-v=vѰc3nS&$ZNk;nd4> !Y(  8TU455*MMwpILNbAQX'LE;P^'ݳ$M@QzFKfAƖ:zdVe7~< z$ESy Rh S124FZш7]C;4T4bEar Q:Yg &91yQ4T4rTV5Ƨ3^HQ{@4؜$ 2J(`ge $g'c/횧r 9id'Gb!1D:c)+Raj|Tx6&63YY)F% `".)1 vV%hIرō(#NB֙HɸLeMXg: dBXOk6yG9ԛOAJt`Ra;M+Y).ث)饘tLk)m!>- gcMLMo:T6JK)k=Vh[>uRmDFO FI`7RRl96 NT!漻Q~U0`W"*1cE3656Q$CcR*e=VY 噻@f,&e:gjKәEc9۱K**ǃnGY̠#nfVHR)aÎx]|T= ЖD} ] m)v3rlqfzYFr7j:⠡Jt꠸q:l%=tGQ rn׵޽.Eֽd:ŅGQ7NE&)P<]z)@ފ e (@ZN"e[hJI^Bu7SR 0sH˫|u'#IAΫͪڄHΨ<;Xv2ObR 1>Bm5Nkh7i%=F})L C$1 $z8/-fbǥaT[^oW>|PZ}#*IM 93TWcvyϫ0nXdYGUUY=֖NF*+A v[B U̎2+VR=T9H2vzRۆW6Z@ nA[DFTI!*&\h͛A/d.EE#%Pa/v˗Dקe늄E~=;l$䚰WРKcrw5ryI$꺗ޣށ{mMHH ƥqڰZZhoL!PBM`?iU7wqT7Ka"z IƼ6%vB#jӇ{?>tB{c0 l$c6غu QFSnޛ3mi\]GlߺфNxR4F1fMx 'ǃK|dbҊg 61A B;Pz+d&褾#gT;X)rEzMmJ4 e:b%=xRs=:cKVlۧUl68GC=$bzM?RCD8w+8zo:OxDndѲ,HKCZ-lbڬkFfZ a v cx>V3mKA8hVk4-dɅLO?R@ $dm΂SQQm5o)~ zn (Nl*u=ГVkQPbGrI 8Yōޜ@+E뾕_Q(^WX_޲9 Vq ) V/^KKVV,U;g1rv)c4oY6:Iy)-_vW'/AEu)>R&3;: cзc4ض=iHӘV"mD>b֔XqwcIx$`%XܡqFAUM;S1Iݫ.D6*7t3~Tn Ӈp]nLPV[7;v$nm9b1TJKk+;ʨgΜ$&&tpo5ȒNh4Q~Gzj[?AhxZ[q'?{'Uj?U9Mvw6KVDlL2` 滎m>c0؆9c0| (icOwO ٝ=]==auj{Tu{Ns.dEom̃qzq [kX,44㡱IxGtwPS[b#&H(BC] <<b`p ouہ"8@(q-=Xvr9,x^Z[=/sEO4Er9)i8p@ Ȼv'O>l{Vqx+_bhXz8hN ceiî@)g 'R1͒@ [,Zޛo" A. Y ml- ^)ɢe{eS |-] l1cGxt]GuTUu$Ybr Q?^K7ql6;x<> )U% 07O*ȿ@ 5EoiRCSUb-m4H2s3S X$hniX4yӔ ΞɞET#G#|8MP/t/QJ9KNu7P:1~um|)S\t'Ξ㉳Osgɥno)\6#3(6;:*(G%0\@PXÁÇ~ ;Re KdD8=1d܉U.|n.R\w>9J)g)I{By>N1(ӈг䥥8] z,ý :_&ׂ\8MmK囑fDSZ9PuBK!:"4pt֎ ts䢦Rgfj/kۺFdjbas2cfrRNSlԫ{ven)cLAF*3t/2J*%IG vFx$Ղfc~~TZHjdx2${$ ^*.29VF-avbeTن.21ƅdhtŘ VZ\{*4o#<:I&f Ttd,NM4cddH]⮨jOvRN뗦+8FbIfI8|T5p(V `&:zO6jˌ\'|deYk+e>Dr|()xhnĘbl~a]DGs|3d(M&`wQUWOߑv xAH67&Zj5Lt86~&)qꜙJ:I6ʬ)&ohA{&4qٍE2;Om>,to 8䳢/q?0Hyߺs07^u54ʵ(Ct(hĖ&frv_ڍ=s8ڹ+I" $yYzO@M [p(:hY[(SUEqVSgb5gXcVWA /Y=g$VʉVJ*D=SjSʚ9FQL7x Ж&}̺8vU-I44˾ִ7k:m$eut4D:>O;PYƨ9Hk(x\ )64c$:8VDFlN?m~@%]m=e5eT 36܊͈3oI=g$I:ۑ ,n˙gΕ󊋺 V6-uPåk )|Xd#gycNd 0ꪠ%ud]۲gq1O;+H+ZAy*XնjV[$Ru49vǡP\RUx%%k݂rSVSmL]lÞƕ,mUok3JϺnk,emnb 2'Ycpȉ@{.)uVf2.l6cI'z<{|mr+boI0 0 0=;i' hQrc7Oc(QCllBOF`btn+Doozeu$ch9xsW9k6oY>HVYCtP֚&d d VE'+%oI&k$RllQ<5T[QVo'>3Ƣ\1rE2в<}ZbC7nmtlBvi>IWIc,/GXJcmXEݶd5UW&b;l6T ۦ&Cr [ 7*j[3Ίٸ[h9I[;D,(9,++rIZsȊdӉa@?Y7}X⢺l{u%oc_\ n6jH~"jܙb!%WYMʯ8S, aR I"H˾^vHY򖝔{R,[CqPU#9;"*޺tbA\ %RX 0̛YڄX}TTш=HTo 䫋سٸz:")fb= I8}%d[OKVZ5bVY4wKm9eN{& hE Cv9 . 9$"n3=.핃5 lymX![8N3,I-'45`ĉ]&KֲF 00G3d| EHd98H0KjvY {}6+LY# $U MM0NXcddg0qUCSgG\gÐa2]M1:0.ʖ0R" Ri(nFE03#,2sP˗PdlvD MђQ$>IJ[۱gqe;C3a)-nf):zX]t e<쮏KҵA&"ē*$ex`\d1@V/uV2w mڥ'qiR5•$d]^CKh",6o@iz2@{m-o]Eu(jkMsnUm4pŎ&G\v|ZkLL;'edW.R6 Zi]lǞMƕIN5#e<l"z7\Uh;"sk2:+["87z C<_)u"~LۺI?(1&FѴlCu9r(^\!0:>N s}#*U]DI6YhZuM{-]%4MO/4s4׃$$ ff% QWWGC].;`aFF¸x],#L-a9#H@pkА7g2}-vܝ@}6Y(H ]Ͼ]$Ou[Ǝv~n[1hRp id)/ߊu#y;Z ؒ>K|ǩsJ0İ\1nW_,;Yy[nVA>NÞ7qn an~э`k)-1“%ha<7 C׉%耲C_wo{+V:FjWOPS}~^˯yU_^ 7gJik?$x_v{|?[>sL|s黤u'^/_;Ov3?*~κܾKPrĔNj[6qrݜ~UJ  g?Tk^E5(ѧ~K_>ގ;2#_?gjCGqVj!5~HߏƧS+/rGN!?'>/>o/O 3o܌3|o?9xw9&\k|72;O9r{|^Бx_ClaJh2m6l_<6uSZ(죿eJ+Ԅ3[x ȥO_=~F|y|#A~vb22;?u?k7z C,^ &~֦f>#g|WqLc/,_zf%l'yuxo췣G>~oUN:#=CPzV:<PLԕܕ21I_[WM{⤪ǙT3\=IÊUT\}diaQkeۏO3Ȥx_Uśmf|z!z, -J2[yT8,8no7G7AÂ$x}$c&VC2QN_yS3[ %0#T-<;Cۑ,;N=ȹo\ia⨠xh43i`]e;:KL3qmG2c/~;_%K!z(Df~ifukk_?ZguaF1Ύ𳯸 `VsΗwn#:ڛy~׼x|YJsv^7hi^NMxw߾IPv;sטJ[^ VI{oxbyr"ͼq(/{5ݿ82f'|t%sd 'j +vbmumvaY7g̳g,R ѯIm|S4 | yKZftlw|tͧ[ ߇7}z*jej|XP\f"bxUJ1='iz>~>~ic G_fҢ,rn '*p -(?zכzt# ;+V,J e+Q%/wK>/gS%)=|neZΆz8y괋]ʕ,ᔝ2G[rq$L^Jw̕[މM ,gQD{)Tfkdtf>vO_;2z)#Y@bՅ~+OO W4q@'y./oq>KӔ[Z9Y-H)sd^ϩe|uW5Uy|DTYk=ʛ)I4i%81i]ע DL8IB"sɱy\Gxͯu[㑳$+E#ɵVz1ȭio;\rYNK,_"a%$=W +-D |4Kcl 0^~z,Q џ%?s1/qtT@ׅ)$hxogT!{ow=+oCW1E{6%J7˟E?`U}"JRy4ȣ>lkζE=7I wi/- WBLS'||j9Jjf4*5@oK+,أp"k}EIixpz']'xV7V٩Iȓ3TzqmiQR*ZxUx|; yՋi]Ik+D]c꡺85￁`W(zǦ, 츽eLOO"rh?qT<K1yzd9 |SPagrC,mY&K~Q?Ig'_{>:N z>V?MY]'k;җۙ?' >%>ӔZnvnP{|{]DUR9}a>v~o#o/ԫx*yǸzxi;uaFDZe$uDhs!sMwPo+G ,qxS</aOuP}5]-׾q?Rww,@̤r9י|S#ƻ_^?M&rkw;w~b{_Co?}Kfr<|;y8*:_:g3ybw 7eT\ukͷw=_sMp]o =鯽_: Wݝ͐dKFUeX,aӁn#ek|#joQ?5ϩ"@kƬ$`kxndYtp8L}kAý}nK"@ (%$#LvdN@L<@ [/Z;- @ X!%`OQ~@pKcX,@ l1%VGLi @ Q@|@ V)1%`OQ~D >*x@ lKK!l6;%6 IoL$RIttPr GLi JN(߁mqn\zw[@ (cJK#X8'o?šN.ns {{8zn#Ec_|ZB X,IaH$륵L bJKPrv;޲cqFGFhmkѣY8-xPZwA^bQrTG)rJ}u'cu+RQ@}} cZ`dxxy@'f Ο'gOsgP}vVX,ywЃ\={."GR'gQP:#]<Թt<{ W{~Ӳ@l8]]*+yڕ+LOo1ET/P4w>ũFYdg%E(=ZXorYb&g'昌;;Z0ãhjڏ]wrn*Cha\ã9vZ R=/-n:ŋx^N, Ϝ'?.p{ ~M_gBv*m+M2o%͇gJ0%0C|tv(ԘZՋFdjKubV+wGM+V\eմԹt}H2Q+s: gYX]>i*rEOE=V.OSa rJ:Xb|t2If+7 3;>2l@sߠh#%Km}ZsL2YΕ˒Zu'H 16%_M38#CEHL*3~Hq:Q.th÷nE/ɾYi+霨e1Жd6<K8jqtORe]u)d<䤶6Wlܷn)^UK##<W^ĂFli.RQ٨.69J:g65-i)á$LO)oL }]Xk8ƢFkmmM%;_cd#U;-Qfj{eԥaSܚ ahd %bo Ѳ&NQ0}L8B8Zsgb 2`;H0ˌʠ&F"kMq /'gDp~Y|V1q:‡%9G Uh <ƳaMw|黙{f'j]u.8?C} 1{p6]zdO 8t;9S8.^IfSӹ/|$F\V(owH274-ʉfV95L)Ptoe=tI8VoV+Z' $ʪ)MoK=CjцG`Nz| mvȃ$1A5qӕwfdӉLQfI1QLmWgfNurgi$AF'*dDȌ,G7EMh-wcedيCM6ƃ$WNcʋ]e;:|1z>AX5^SÌ5T̖s]^o͵$VZnlb+9:Ϭր'}꣡ma)HipbdJ[=Wd,DV*H;-[oq39͠EzOrӂ,[pVrb,0LjWXEާ/?ɱr%s^'bv H0=r.6) 6 'N$n$Em?mm\V"v;'t:7tv\˩w]#*8,R6H QLK>Y"V5dlMR{io󓘞#=_Gyre}D%;uNdSlE5io 4:V{^$)ig|5P01'S]ڨX:+o5c?kA>yb‘wd`n{ < T̈́w Efܼ'fGi* -VT}n첝@65qrqcV~۪0 {*#lc166G8(>Z[+D,~d 5eU,.겜B efjڒ_#:̨?Aʸ1m#9 lG+r]3cgڀs$,22 xZ"正.GD(Ndj61T@evjAS$' :j66b+ΊG!69v]{ם}IwKL=a;A,e.M6`6Pu"o#Yl(z .cd ga1y#OTBE87QYfHӆE\ޘft$U~Y"a!F'%$d9Wl|7S[ׅ3OI:k2sl(/-`+(.͉㣼 wZ9t{-VP7zق}}|YA G"1{õlJlqm\T$:-8-:q|oVۚdaҦM3 $I-MŚ2Hkq\`1UF cLh:ʰɵ (چ G#+AR7}lV:_;ue(g~2Y-:+or{P,|&4cɋoo Qi0vb̈&v tu 5&[10P:I5KT2=l7G C`5⪎뮬/3x$ԗbuAQSGCQ&G8Yd0Y:CB TMǐח9_\_}^Rܴ9J-[q%G*XH$dO-r@Y Y46Xo\ײ];ǂQg=gT+vE#2X߻huf+DQ\Y>,I>af2RG#ٚD`lr vmִ n-7L@-8R1d~cR. InjuHeQZ⦏ͦur**^",SQyrotCË]C <0Uƃ[Av'_Jm -pp$suS*:9eOX'y0u=J\)uy4HN0gx:͖s5 %Ap :e.A+z.[p}T4r`\޵)e{kdž &$$ U#$ H 07DP%[b9aH1s$]~\J<2G]鋗^:j,dy-|l-N\[$j0W]bNQ}\ZHۛea"=yyq'I&$IΟ=a!sCqVez ⪆#^XX53vf6mن[IIơ/-161DGCu3B/f. э)[(kEa`6 B3ka^9%5G]wZRl'|YC1!j蒁 ͏:~j3ߊGف)-0]7X;:)W@5smn.N}ʍE5>$32SCI يkp*Y@8tDa|t$ >jpa%N|pO નȕt:̉좶pwS imxS4eSa2)Սly3̝5>02I4$ LNr}(='Yx+j8Q-ͬ\M8#uss%CuN0M (>p9X;w۸~)~22zZuRiM3X~\v8~G6/;¹sIN9'$rǝwftp)D8k:8elj+Zي[NZ|rl-}25Lؙf2mAMKT$lVuFz+.# E &. cB7  ٫9ԩ022O+Wgrʤ.Mb+ۂ jJ/(C\_>k:Sdg/SJ}]׌q4m 204ϼA&'16-R47|ZB vvc>؟1%@ t cO-`baZ@ AhJK,v@pkS)-r/D  GE@ nyDG Q)-`,B "[0 IoLѓ):|\@`O ĩӛ~?JPE.?}΃z-@ %ZxKP8c:;;WՋH 9)Jя(EGP8$ֆGBaD"7ֶvz2mS >WX,ee-ƾڕ+<#t]31lJӲ>~#[RY@cEʄ8Mm+Ŗ* o'˰*1EUS }ԲXZt@_#l6p4d +I $sO2ي맪2j);Q|yd8X5I).ts-?VГ N?Lu/mq3Xat:HT榢=DW7c*(ۻ踲߷ԫ}S/d[e^@4fNrIHC@H$90!@!2BvtH !$!4m.[}WiRJ,*l9zWw]XMmdq0pzDh 0NqŖ'Ydlte Whc+ZM03>2y_Ķ^c~WԀcW M 9OFf~Sy:whS+ag1UZ/] =% PJWs NbjdѹerWJW[a#Ry0<Զl#"EK~γt+:6x)Y COGfRLq΍-\Դp&^\*;?Ep9eF9'|AսpEjqO.lX正y&م ]u:uF̮U٪f#i?-[\8ȓZedhs~6oQY wi!ŻvnT'g8 JtoR$kp3MM61zÍ'P* )A:^ɦ9FQmo'61t-?ϙ3ϭNT"j  lj"imq#ABl]Tu_9\NQVdm`Ow28Dΐ Mfp W]DJLD:i-b 6+˱iLjތAPT`IDATVu*Inch4uQ%uB+([+8FDzm^$v*Nƴ./C^UU*<ruk,]%'&h :.- 26'D.L+5ibhhĚɜ@y2FY)9pgc9',aŦrULp%搵jI>@z#)rvAX-E03BId0e$X2Y["^zumzq6#B<[^iy_x? My-RߕĦclØ:2,3 ][SY{`wĉVi,$~zecn"XH)r$&9a+-,7K-UƁ4y /KflR4'/uK4ScMN|(],Kb+BW$NthC3 'GClkw2;SHrChOs*h'8ϱ< 姶}M[e1Ѱ!<`N$TDG+ "~]&#cCbolħo >]N8|m}cO\$7cW=G}8 cvpgN?_Fnbn3Nur>3')lIi'{;95 q;9z0G}ȳϒJ& B>\G],#.|&n_zF5زL8emk ?r#>N2q+=Wve_\thx 9Y)KZ.'eJWqw}r1g]'; P6..:#NX]VY)&sOʙ l lfrz$6;,j==E{'Ex߄ñ(e!N8 ѳo_ɹrvryQ剢`NMmGøf~064R|.3tХϋ$,K3䂍X\m*גfUmės 'e鼼YDQ^hV JCeVfbB+(XYi}6f/r q4,_=|j[r| {qSٷ֒X+K1G7U( H!./j綮H!$EwݱϗJsM \8?R*;\ XN:OJQ剢0 ^ŻXZ)C0&s4pX%!Ͳ Dp(KEQn+NKsW4- PʓeYHb1 Z,TU˅(7)=U]Z'd_ʕ.,FgCQEa0448hBLQEQhZaвAEWjSK'-fsEQEQcxX1eGQEQ*]ZYA˗((re$Gܼ-kZt((АF׵eVkvREQ喳2{ .SK@ug7UEQ"DYթ%6hYtjQEQ hZժ)ս-]ݒ((JUTtYA<((/Pk+REQEyREQEQny£((-OUxEQE ((Y!L]76}][ ]1&#gv3MUzTGQ61-6wdзǿJ'b/[O{?D]jdȵ6>Ǧm|*<(mѹ\6C"Y!,ˢsv%oۘYlOM"Ͱ܀^–NGZ,to՛يQEQX"{4LFgɤS 3 BY'^>zL&E6}zʤ %??P_aUQEmٶR6:I XZ1ִ$u,-dnx]OLƀ)!)( UQEMI)IgH!R !D!&%b!)R73d8ɯ h ^*K .E!vVJ (RW“L˲*l>1$3dljilm޷esMw!f؍̍o CU Vx#'a?pM,,8y~v <Ǿ3'eg 㕯#W%;v,ů{^]ʫnrEQU,//SOŷxC8rrBzz[[ntD.ç۶u“sDRh_m=tE]\ _H2K,#Խ8\Nf!,]xwuhma>Lÿ޾u2+|D]Yf u'$RlT-<(=y[RO~iK}}=¶y?^gEjޡ{vdc8܄; 9{1o-nZ%̅5;v0A.\R+i3Yb$o{(W~ ?,fNS eǾ䝟-ؼe2~ohxkH@")/UQEQu9@߼̈́[jՐ2xN@,,_'?9O63?#{@fgx!7E#|@g~?~lD?O|_6')xO5=?| @~|uwy-<3}{nH=ʇ>~}>w_>§𛇂#Һ$PEQصP^ahx24`i= N"Ƕm{[/rlZ5]# *?/#;1o-ٟ67䏟b~96<"> h}';/ͫg{ H1oig3-a^c?K=kz {</~[>;h)c?dY.-EQ:APqjjjp8D"&''Dp\ضA}`Qձկ~Q*lHWEǙ)hTB[l`oX\Bpa}D6C`;GIO+j25“~>u%}/KyQ/m\Ӻl=@t3m ϭ_0ύy/עZq'3GKH+-EQj=RSSaFk4T:E2dvf{MMMN=O*k#p2"[V:s飥.:"VEC-<"?S8{iP*w޾o ?eHrgYz;is<^M"u/QoS'&/$e2nJN {.Aae.-EQ d-<={ٙY1 ]fLMNqߡ(MpF48]TX$ElO3NV\LE;WE-=BʼK C=v6NŖ`ꕈ\G&s~չyfwv$x,ojc_xd|-)( B+\kJD,.HM3R`"|x8R4|M< n5EQ庒R *|Nn'x<4Mcxx:@mmO467d2_r|r9Hԅ&Ey8oxih17t|/DwUd/{ _b'ǖv$5r ^E.v̋Ic(V"VvS_ S!sdlSI1䓌gWZj<f5qgO??KnoүWEQ ϱ5Ο?ӧ~+NzIfr&I83F@ %YiYk~$y/Y6 ^fv#ǖ%x[ :Jb {/al |C1&3y)>zD:Z$'XȐKLs3ڨwRxJKۺLz7yx+GO$<}ɿDηG?~d\r|_b{*.-EQAv6P#۝KL183FN Ҿ۸@֎1470>8€h#faA+I&?)Oin y Bؘ2@!|֏}̴7h|k͇>=bBヒ_ײ%Z?9>ο"^ϻf Yaˌ/~'σa//ϱYXjO|w| ʯz4&5]x͛N@6^xWAtC ZVEQچjEQEQn-BH$9}}XxotEQEQJ%G|~/2~L2FQQEQ媬5̓dG;oCb ffI1GNй9]L W|rOx5 ǴS?LliMJNGm"2"7sjnԼpϰsNp( T'8JiidWcA y~ZQFۜcnGRoSQwbq"*b&'f7`[C5}OОv1Yr.3s33F.wR7vuB#t;xBR0@=T3Ӳ̄|əA'~1Ԟ0n桜YrU{rc6Ǽ~O1#Vg~&%IwELw wB;â!XзU ffT2g줜%UF2jWu˽Nsh(c3Jy| gbہAl9[) ?126'$@"ef t~˗Z@,k3IMͬ ]GZkH]S[]D*ߨjWoIpJ$qq?U**vfjD>8)1$3~23d3"m  VƇM\ oP㤑^޾!_?cj5i׌ffzBX"B(H a'sOKUҗ *5 ?3, WzC}1*%3gi^vڍLQD$\z8uFE|>탬^hԉw;N],wb=wbϣO%& ?gu/ ?]0ŀ x~t߇r=l.ykLH-n箾w'lkGV[?a{)sYTYu-7bs}Uio9>ꪣ*@>qzsO{xc`+_%F^ 忺)[f+< /<˛"[8CN2k')$ 2gۻZ,;l`C 66ۭ0y%@e lnqKaC\BnkYsֵM͹{>Dّ Y虫X99:fb iڿ5c]>2"W3Wm{cSG??&5/|#ϗ>{tf08+:&Yu{~<=cmVݸq\>665Vxvε2pm)BDʓl۹B65coj cYv.lH$:5m__l$:V%y2yi0JqG'yo=Zs{ ̓O3H{up#&)3'!>ZKC"esk!ۖ]:Ei"G|۲b <:'G$o@ؘl&1݂xw?ѯ鏫!>'Y\ ˇmؼM)f5zMoRGfV.Rv̜%90Z~nB {Wy8'lhB>#ϐ2g`q8Ϝ2I5Dlײ@ ײn9"ŬF)nȤnee:+CR"7)_޴zm] Ǣkfݦc֜je+`)c}8c̚SͽlK86YH~{hQi`'otR6ɷ B[BN%ٹ`K Ŋ(q<}#:Es:ؠz1JM 3ɯ쟾JbמᏓ:)Vn1̼[w|.~ͱSZѫ<>M|ymf?kH@dV?aek "KV=PF$s3#Y5:?k}dd~c*|@չSzfS@Seکk5 Z^s3Wl)QOT7|z0$]Mj_=f邿lo?W! Ye? J1s6äٝy@0uv-_7Uq{ZtbB) jcݞ3zVfH`a!_h*UHHH]I,(*d&!7㑾6=>O-VdS$ TDm.?[bE,'L[2,),]QZi9|M\X2pDj]`lJn6jg77B0"6gzt]t띦Ot{P!b*XE5"W"1SEO43K ]UX MAffiXW4JıjGt͕dulewz,+L ƟBL'hV )Mx{|k˦{LQĻvT7A Wwwv닂{@RIN¿vNj_w֎߿yiދHV[_Z ֏߿ N|I iؙ4hߠgäOG[N1Fս5waܿ t0o_kk[[ReLd4t<9e˖GYYYعfiVD[)UZW"$i궤.޹srv/g%P;'-3W䜜«SGޝ$Bwp;tfe&}q {n oϟvFں=Sq88IԴcG#isgϝ8qb۰aj.ՍUM0^|9O-.ϦV\8:u,xz|Ec۾vkg$snmܙZ¸) SS:Ћx=e;'0y!;rIgkA 6h0~Œ 4hy|VVAwk\L]٭{pŊ"""Nݻ%K$9  ʤR=ğٷcԁQMd2ab_aɇO>ɸl:wW##ޫ5#Gl0sC>9eIe*1,]>^pnEHyti%/PEȒR̼n/kVO߫36UKU ǐth$=)~d i@BVd[.>N&wGJzw_VyNϥ%άT"5wӭ[ׄxs$'j'Lw$ $pcux, 68.xq* [3wEǹ~1r7@9cVwo夠i jC3 $5ǭ|єE`vԊ5zvg%l55noMMUq szU]NfP{4`/+ϓ[R|ԥkb/mvv{ڵY,VZJ20 ^zST vmim#+>{[Zu]9CWAɊ6e8ax왊+uzhIDLlljZZ&M (=,MJRZk{ܜݻ{Y׽o3f`XYY%'&]|fZYL65+.|JCiаNOWvqXY"#=<~NNnn~>7hOPU1qKK (R'盛q\q^42py<WN=aҩ^!I[L@}p#iFkg};a424h̤Aa(s4hРbJ26qIv9F1(A;<*?24!>&.*k;8:qȥRi"msfj}|bqB\7>_JkRik?٧ @ P$"!A H(Ff} 2.TiK4'ed,ъP :D &>uT\\iJJ*6u=7|-*m"M7$6 h(a9l\6jaM7K6K hبI! OR*Ay<@,IkjL ỵ w lаIA=O!4(axQ7r/*+ИY IR&ӧoذAdR'H׍={&^ݣJ؈j(a O}&֙փm~j6#nVi]ۓگrgv_9!AD0{|>v? ٣=|er6F&HYLk/ *A3VlYIv 1{$-?ToȜY2|‰78 y s0F迫Ճm{5AxԾzrjCij$ yƟ G1T)636~KԵ6D#O4gj=~<3h>= ?ȇH6o^;ш󸆚Tyyt5[p:;~~f Tjm. {1W~`oJ۷]ͥ۷5k٬`0`[I y.mb\ r*p*CCspbh"6Ƀݏ>)>dXVmOOMZPjkk 3r??B㦦_lJ$LGL-^xVmffd<~lmixd:q<3BP[_ l )U-MMXlΓ'OJ7PyF0(`VIIIW\Y]T L͵}}xZZY2Fu?~8v^Kk*({OͭTk 7] 4{WvQ2ؙ㸥uppPk |3Z6c|g61 4zWD%hknXX%hi(=i%h4hfҠA3 43i((鼬:% e٢mb{{ĉB.imb4JjZV{JBa4wp ikЃ"礧;:;IDbH6X$ENԧ=uC`\H%m2h2L RRS|D"ѯ}xSSӔT={XtE[/Vէu``@`gl]fv<:7}jZ[DR&1-٭i``@sg(;)b7kfܙM^uW$IR.&3gܸq;wHdTıu6qƖ<_marBb˷8uRSO2A+3Ԅk/у[<~CBY[eڲ&Tĵ[7O-cru3eYߢ×oݻ/W8ӽؙICK~ަM[УI=z$›@O3u``@>SMG~Tqw6NnغϬE"dS^>3=?wcR+6"z ?I@TIHk7t4p_bk+(.23a˧wdfp]j`r> ɃqDku~WkP6flzL!į;c^|6׺RC"cƨgo~3]yaKy_+:=Q3@'qwfg&{e-ѻܾsm*?n}uҰ:|uQxnݺ] !O||CgCT; I.V|9mMxY:Idn92ÃDn*Oʓ_:ċk{]XVoE(-=mڴ1338^x,k5+Aݣ K8+zմf=őobݗi?z๱‰lG{Ҟ{ie;S+6iq1?S}$=h\*VZ$yYjzP{9.!j =`K.et_6# (\|>0{]j gjC,LϽ뮸fz7nرsڴ%Ϟmڴð,pW Íh50B'3&N5yƁM{("ͺy|9 $&Ύ[u⁖ϷNӮiИ5w4IɜQ`8,5 <Թ.5ꃇܿunXKk T.t6!%9"RokZ2dĤ/kWQ]p\[ Z{I]2!Ե[ *DFFN1K-'Irq z}ZmbS^zDRppRDJp#w>xq_X F=ŜyTt n^ߺqtõ?Ȩ"lF7%&ܮϗ C SG~ZXbޓL+G.xT1X[[`AJХڜJ×Mشdqys۶IX7 Wuvs{+PԹ֣{OMŀ vf(K[}ToB#thhh59,VZj20 ZnRr*A[&0h>ZJEM\&䵶`0N.gOPרEL$cccSҚ4mK4L&%9Q^YZ[ZX8;;3 Nkm"Rq+K䤤+/׬Y.CXT L-}&ǽZYZ?4&M~zp~Z;/߬m lC3|{tt{ө{qs3s'GGWWW~y&.@[ڿ,1q .ko Qjy|33SZ6b&%NN43J-3UAF`s'+tAƩZ (' 1@FiL4hfҠA0Ӵ61 :7T*fkgϣ>q"J&oJEzy|7o61 zPJB''H( iEb1=]h\:N|Xş %L RSST"֯}83ST=_7)46JJ7mڤi&M=rbvM6i"hȬ-4™=Bvt熓@`Rk )K|j셇kSQr~֛I[֦M y<qOIBs+m]unإQz $Uܽ6}6s>,_$?_&O ZF]Z80MɚEL3g{1oݻ{7㺳:@UUcO߈q:Zp+u3ׯezp/ n =n=K0XVزrێο^ȁ1p|*T}9SZʸ!vEӺ8ǻ}CM?*Ol8OP+ϢE*B`ߒ_i6?^936uKSۦ! y{W*XIN}5@Q`0.ngNAZ&&o_nܤXb`… SjL,X,ΓϬ8#G csQ6-[6fəo@gǓ:{+y7s- =%Ӟ:՝LLK6#LHO=2c8ϮF OQHnRDk箧OZZZnܴb鉟L|q7b _.y[wS)Ѥ?k]X{]9/Momr(AHdz9yc}ؾ^ydar; /SKPsхo  ?DX̎'~r1ƾ3_5U /ȧP\ѩ T4#"N:.L'.^tYbJ6&Y%SMjYi ѾE-Zno$17r9($ q׎[e~/wԥu ū:-$ԙpS+җ7n߼q,C @f(KgϙǕV$9z Qa׼% S;029S992Ead3j7od\77nݠ{3u6J7k>6"dMmrܺqn4&1X 5:&BJ%hs33{GW+AS!"U67q9vRj%h>ofjp釧i( fj\MF)b&=4Șe):;Q{:PiޭBoWm=zcBj:<~B.yR`Xxkw؀fn&ΘY0x;w]Xxfy#>uXYuKwމr; uXHz{Ko`j}Sio_JGa3'taǣC[v'mg khd益8e()|,6pf88=vL{"cIqKVë<2tesmktqzǯK 5F;˱m7>Ċ9V>]g._ݛ}~<(훻!g.]J;`55zF|=a_v/؛jsLz9R'òO;߳cÙmQ1n$BkL{Z쟿V.oKWn|'iLʩs%}:yy.1Ftm׺mK#Kyi"I^L?ZQdX8=۷ǼU+wq=j8^mcV E.{qͼZ R=ڒ2lĵ.cUp)ol%::nDMMcUp b?כqvܰoo爇QT̷ȹi$NNyV NͿʇIf锶Zm׺mK֮uvm [ήYQ4ǴwFݾTiXP඾+u~Dٔ%nvDq2:zIaeu35X֞Vr+g׾ݳFT2v&vL`ZyfdZy? ߟܰ쓰efBR@$U VnyZym{qpy=ηd˓%$oU>2t5\ވ*5/YÅA2̢<Dzlگ׹7z2LG3#G ̩7/-"YhAuX;^zd*Д *fV%eA_ONٰqi5i|gu0e P|ٽH0eX϶s[JQ*S>|gw mZyqd!ϓn=hʋ@j9 EH32#T>}u&nߨ5[BRqc_psl0|>IդJ~~ٲr+hiR ;lL`{_4m{}!_ac*qI8,}!R\̷k;1]`5^ֈOWeCv34;𥇡̣^a5^ݱO gˡuJ'3 $b.Z_+&u(~~~tܩ~xFUI%>:謾5_[ tBwYCk2S?Zd!gڞLPF3a.r}xkFo+jb5=\Ĩ 4A'3_@gxYLMO+'CbfZxڰu= OX 2& VcvfR9h̴vL?MKy3R5FIY›S˗-;뷦+U?zX݋֡יP/f1g`FތUpf:y _c 9sCT&$UcİW+F=@gXS;{ ST9UX"/}WX5؛n4>̕#8ক&, 俞li_@ݩ5xZ>?9` VZx֣SgSaIǣ'[hiL}'&Je\eo7>{Dy(i=g<lm[},'TؗGJ҇@nϾ*H,9ޑ?jvZ1!T7wԛKO7KtH4 *hy;l9nV M &ݺ}r(b;2dtnMSa1;֬P 8u**ti˟?6 V}% TaU~lzzFɱ{_{YW ޽GTV(d7gy:xiIj]͠jtBlZE{{t>䒆jr+v|c,Z VhWERڷAs:;1o;qhܚ&NgMjdA,^<~`oZѫ߀MӠ@la)R &]ʱC|h'xˏgTҏߩNL4hfҠA001_9YFСA 22IFiL4J#]!BMUr[,68 [QL;Z`2d_J feo2JR,9!CbҦ.PZj\oR@M͂VU*\. AG=2,dԑԥԪ7jBL&e啖 UI@ёVVVˍU^%|V_M\.#G&?Ŏ HQ& 9SIˎU Ɨ1f P'>=qgQBaӨc!UB"KNF(e%ArDnTyeaU{ f҂X0~o7I"{fZ~@ËO~4J @ap#_S>uuxױCoMU d҂"eɪ%7[wM2L(c^r龰9rnTMM Ukw\z:7;&eI)cZ$ҶniS;j$)=ɁiS=h촡xvB!E.dQmPZ] Ii&0A*ttxqyǵ [ZдI| ?!V$)愭OZޓ}moD&A?}pn3X%TTsŋss|^^yߴ]Ξ;iNjH(SK_nTtd^IHy=m/irdUYRb`EF$Bi w2gJڹϘ)I"Y{X]kpئ~;RF" DP{]{8:-%po:p@V\={m'3s(J;!=ad^cL+8<힯I{h (oV-HFf1dJ$*ND$ dnU+ZQON[nʴؤ.H $D$IJxY-KK=}]$d&AJvW0lZNI>ma Ϥr&"˛UB$I8nv1c,$I"PI#Ksn^tR%yqv,7z#Td)O^LjE˽UC{}㘳#d  c9<>]q(Ti%AO) ~ 052׫A^Ĉ Q |`|MTάJ"B1VJdOaˑGtILcO;sXk?̇C22cDdz>lucmdzI<۷/Vef[HĵA&0Qzzo41%jmeUA8D;I0>ݛwu?ܣNS'dfnp^|aVYMM*(U6VE!qL ӿV,SoܸVj {[[n߾SVHĄO>Y,sGTg ~URxh@c&A*&<1'D@*9;9x<~QghUlALJ`ffwL./q&&.M,VEj0_ALͭߠjes9b/"e2/SF"@$Ali,b`@j$:òMW2+S."Djf) 3A*4hFLD0iI@h01I`f a9 4faaa0BRA QF> Ѡa(iŸ3 iZҠa-R8FXvq   8M4c0Aȁ*ma-}4hF̤A4I/i(L4J#}I4J#3iK ߣ\c2YCH-@L&Sȥ^ff43iDz [\+R_<}Rhr,ߠJ*|oI?PRL]~v=>(IkjfV 6vf,%S33ߊ_?~(H%>j4"#dy7xRvkɃ o>%~zM?r |˵AGN{N(Ȋ.ݮ suNK%J&P#(G %Rvߴ+9;Uk6po2eiai Pƽʏ.D3?*ukho"e×o}dECbK\ZR xbVLlVUzpuvK׉ ??vUE].%}\ӳUuC|x~i'eOͯ 5K0i[NU7c^Lz;JrP#{. Uz.=h`?fo{ec ǜo3:x=Z ӫv}:z[ua\s4 U?_E[4e$wVr3ߌͧ,fdkv490ca ]XVf\AHq˖gnܽe\mFH GBZ!5[ʰ+BZ7wh?ՏmC{fNpz|F-psE{bZBb`ҐA'>?ZpnTɃvwy;B GD|8kĜB ١[goM>u䥱y}9ѺgQHX2Wk8fbV:Y%f̌>s Wp'QpfXeV%Ҍۮ=+/RURL#' f]yl;uρ*(:Eg:9ZĶ04y0&F(UH_$Mith>3ML_>ggcm+oSڴŵéI=uڏŸ"%Vȱ5aL=;,׶*;|q E7=&lccs>DEUj3kg&4z5v'Y6'γ=`OWb=ڻ]f™5^ɂ ʸ< @6pF YV:ƓÝmU[ u=`(EިLWu`*>$f*N5D;nQK͸M4%T25ڍ}˘˷BTjy+{OF e @w]|KV+Ң^vcYn^GS5;&J R%sw*zx42-JNUzu8Jf\\W|xX!K-}pdי T2׫չ9Fa홲EXfbk_Ķ ~^$;gm{/?>kuڃ ϣ+T'g,h2WmP0mʺaiJ4å5g=තg-0hJH|Y=UdIeΗ ry‹]swJ\L(۷aO;}zsb߿{پ|1 Ǘ= 0ڎeըSrĵQ_rTY]&y~߁c%mW]Axu7t HUܽvCkrTԓ=kKO&NPr˛\I`V 42븼2nk?}F \[q# U):܃X=z_!2:cs.h?}_j,J*37 ٯ2 fY%m ..˘epE#l6Mۡt י4hF̤Af& 4 }?RLzRL4hJf(@$"HBRX,pΐ,P ٙ-e/%Zd2Y- H/O/B.NO+sp8^!A0T:EY!|+Ver,_*CZZX/Z>,E_=nI>[-e "](♘ DRRKKtT{l)*Zd 7% Ƕb!}ؘ&TWI_/|qLg9rAI[t\D N=ba^ePu9¥$PI D"2gU<;c_oNfW:ɋGl^.\3jIڄË#/m^cܫqq#;xRS4i,&^⸷oxkQK!ůȌW|[qa5yʄЗ7n^VuH-fNd.fXp"I7#IȨ 6ZIأ:HYtIJHWma}"ɟ/[t~#=eWu&4qӾljJp$ gQ"q1jx;7o8Zs+@~Lqzˡ;`޸ې-tK4jD | M:[[upx̆/gA!ѱ:۩jU s]$QҤ-u%m\LZ=2ɮ;& $nZ'`i;3mfA}u!nͭp@7'Z7MIJ^uӶ!s*nvNtc57W0jPxR$1׎PQL?jYҗ7~TcƢ3_Wo}ضNыs@\۲R6ni_ 1^ ={=Ϲ-vJcъn6Ky c a۴ʵG9kqLt'c6FΞʼnrrE^sƫ,RTj.ԙ;UTRbo~2஫h VջZŽywpN- mӛ"ic֥@}qXQ\$$3]055]f͸cWY k֮KHHظi@,P /I u:U<{n?,kF1Oo5T$>x5GVΎ-W#U]_1eı7Vqpy3\s2Ŏ?vU6ڭԹrڄz2owi Dȳ[n Ć/icvCLz<,c;;9Λ7S͐J ,tvrLINҶD$brJ<8$@0L7?{=:]16wsk+d_2Z$kqvk^$Jĩ2R%'pccʔO}ɾ](nؿK bN]g~OVf޲!82;}\!s۟~ j+ף-*eIvS9s*ic֙ٓ'^9\'VW}F&'%ׯۯ_T:jhur1ta 1]whm/$Jxt7f,EMc&0:o6ȋlQUW6 ^ݵyڙ4}B56=b3.[4rFKQuZ+M84G 8kG-6h۔rΨSXI܃}Hۮj۞|2.'gp?lOT:<Cg@qQ̗GޖuJZGUr5d #Fֽ\b,Q#h}~]=rpF8uޅQ,v(TѨݻ~+Epsʸ^xӊQEo\:|ID)ϭPם3==o]} آb͒C4- 8'$[l`fa:"SVJWGqf3q*=}th)?}߰QCd \̹OL\-NOE fi/$鏘hATZVs,=*Sff9"P,$\MLLlmmLL>_KhAO2<0331eC\.Dr:n. 'r=5 0 +˷h43?Yv@hAf& 4hfҠA3 43i((lAffAV[8 rd))@: F pswK$ yb~`ft< (:S(Yڊ$DGYL,H$+kk!5{u>!  L Bi)Djgg'tK#ez\Ϥt ,[RDIU- 96/Je'>;F 1!$W(H@e&B$IB'!CjeA*qrbVs01D&Lm >MN,)@<60)"cDJE^8_DJ09 ӑ +34h3BzI*  $1g_os$'IRuY$!i+Sb#cy\M@* XxޘLG.IbԜ0E1R3ˑ"Ea$bԑđQi&5<9 P#a̝ ҕv j|† u 4O JREl;GӼ)ML\l88ڲũBNN:h &֖l cYڲ B5oS MiRK&oIJMW,$6* sfeN%UW9(2C5~lcY8~F jD Q|O,y\XE"R)x )&S YZ WICη@?BJGH 0R=kլYVͺj֭Y:)MijIqb c1Eqɘ "a'It&atfh(,*l&N]DŽU8@V8n~YXRoc#s l;7Y1B>؛x^1>9vz)Sf.y.ؘbR\SsR0Xf_, 𘐷Q %РA jud <=?O J-ݍ>=K0z6㸉3177c8ennb~j"@g=8nfj}m|&&&|qff`˷t׭U6Qֈ(551ur, . (/C׍ecO024h, A3  z˔ 4hРIL4h̤AVA1VA1VAQ?A_ȉRVթ6#1El`'G=\?glǧ4J(A$!JqaooO Yya^M|x[0Jqҏӊ{/^5>LU SY/OXDžHs43cc."z7@_n;xk2s_]+`g)QqX{5vԮRJ"29-}ワ:yqtƔc^n.`QϷ1rD$BWV=( 72jLkwfTֵ0EreүOwŅ|-,ʼn}ڿ)r۴Ii?g07[wL#IHf%;4q-Wb$daǖ튪:z&7$X#5o_ĺz&fxcfiNxXs_1H]Z&T-nlv~]:;y-SD=uE+Se[dAN﷞mvnp @"ۏ6S:Vb6tc(yJfI3!Gn_o7o媟eiRH%}a}ܝEqLY|m7!IUp8"I^=;_SoapH)U5eWqq"̐cF?+JViY6=3Go#-1^gv`NΦoI`-9|K%!A]+Lkwrdq$MGSwa $I@Lc3tOBu>ѡBPju 'In*ߓ͛RRiR˓8]&|OR!ß$$V8f@rMftiZB؉/d6oH^uOR)K|qzہ29vfs,SFvoӱ{ۍ?4q@jIfPJ}9kjuC®"6p\W';_Ms3,f)}"SpS8W,E =Qlt$TߠL& DM̹\޳gOL|{;;VXi^-Sce GtRMT\@"&Tz$$b 2_ ̲D"XW\BRX$)y61:::ռ)%*,pĔƲ.+\.Dr:_Y,QXff#'ree2׍ehfJKT@ΣAf& 4hfҠA3 43iРI 4hРIL4h̤Af& 449IENDB`kvpm-0.9.10/docbook/mkfs_ext.png0000644000175000017500000014553312770324126016767 0ustar benscottbenscottPNG  IHDRTfBbKGD pHYs  ~IDATx]u|{wb W R-P=w9ݝ9=I.~?GyfΎʲ+ѸcHA\~FE DW.GSͱOiOc!6e9Jym1[/{^Dq92KVKF#,|U*uo, ¼1 }8PA,~c3l.Q,M':c,p GP؍q#7N ;iJBNԈ] 0}O':;bd~5(\_0)\NM^ {9X !%b_$?@0(3w8n2 (')sWvw2"L3 Ϻp/#p]a+{R3n7}N͎;i;? _gqcc;:`1:vGIe%W Ae`2cnzJlia^;իF'rwpO7oSS,#B ^;0++ C% 2"v@CIzG`;/}ufń>ƚ';[?`hMY<jB8ik?G0" Зu0` a04ab"v@C;q^Ay݄I5Ʈё8Ͳs6.98h?V?}~-!c )P0caD| Xc4( s7d="}e.=n^9bk E,5ΔSXtCwq}^y wp5z$>ߺ%|?_ymL0KBǒ`/ٳ:uݸv[2sy"}ytD1.;4m5ov-{l#̧< &{Sy0"v@pe2{P'[O@ئYۼaOsFl7hZmYwtI91' r$3u|N~/JL5 A `7 G|݌߭LnqOvyk\ќ|vVt 0(UH? @,:`owm߾y #bp1te5<.3pYy8lPs/Blqzgwv:-Pn,d :n&hD<L`W=A/N0X; ^8UP0p^{Rslg`C dg7=Be_#+9]mƒ7lUWU[`p?cQnZYNrM(N%eK=tf4]{@e]-)@6m[{.v>{DO "r$g.ݱ絞܄ mm[tn$. _(rM{^Lc^aώ1U3aS#ϭ)Q?Rro2)!p=v(Qf?5r]N8))jQxTwÆYY yjclt뼇'/|C{Lwg{)Ly]\YB:at{pd'ƿ{3O tǞo|GZ̋zʉ!$>͟6wR sW\{NW]ix67*>k.3ETǮ_~)^Wq_g,#7ߢv )Sf]|׃WM !|#":Vo22 O[͍$jno&T)3.kQt%?sD-i9tܣ,egrIb1WʓoQft:z,Pz/oM͋;G?˲%3Is/bKNYr5L U>.<2݅+xfmof|O{5X50:.ϻ>`! u~'DhdcIGfn&[W^8ZK:qowf(z3]8G߲m;3V]yC{?/+JgK ںmm==+7=ݟ6~x_ceO|{] c;VUndU۪Y 6sNȲ޺alǪ~e|ϝ`Kgq˻Yvs qǗ nFch\ɪ/6Gnum-;?hY5~4ƏRSņSD>bKx)Xkiʜ%l>/QUNf뭷A?ί* noeV]0 ?Vio)cn,Gh3߭7۱f`9}/Y|f^(~4m(`{{~s# eG5.l,bDt .(5]^BЂ؂\?ءY0~QiW^;'I&xj Wd3n۶}{3eE5mґ@708iZnody9*%7ܺU@OM8'T+ UO.I߯U`1*,UvLiv򜫯?o[{=yjt%&[S楉c\x_:5,Epd%3>ϴw! 0 }Rn@juRPD*@WESE H0x~m? ѧL'(}=#SoƝ?[8pUiPϾ` =6F2>vm읧DE) L(aBB.?ӵ6xl!6T<~/T$J@< "(@”QJ lK&A;cHDko`&Q;Md^skʋw,?ټ)J]='w~(&-Ȕ8*Uq۫m)++klOM}0׹3[LW|)6V|wshR44uSXI2nPǪ 4aoU:}R He _oڞVɣ ya߇VR>,)0 61 ɼUuԔOwgRfj)+ش'>XWj ̛8!U}Qp d920zחZ2&NHqGH2nWv#elvcsN##N[tZ @ُn3rijK8 cL:emSz{?ƣgċ0/P{~XQ'C Yۍ-śv$}љ`/؅gI .M/`/eT,~q_c?YvY$}-֮u~xw-{q܃?? mɊwd]t1 %vIY3'EI&@y0sJSN@ .?e ƽrao,.][W"ٽSֿ3S.L˖.L/*W ыgyLp@u>ZuY?(]}dVw3K=H}2U츔d}]O/w3Ao/]78V L:/)H=$!"쨨9>rػc^뙥?ܵ7IKA6p1I8C4mHu9 _9ܮ^> _l5dg-z;]5+;U((!`1sRkr.|rmOuagՃ᳇ېb0 0b; M}X7_CR`vGx:Q^Z{y'HwX!Ϲ'x:>A!4c#35!}ljOAǡ=ۅIy#Pjb>)>x9`iW?r@B!Iqc(v;A bR)_#fn B6xQ|W.`*hNMKZzPV\T.x8|qOW.> fW2GXVHfE+)) ]c蘢(aRҒV\.OJN)-)F%h:]gdj5NZC5Rӓu]]k>{l2+GW[Ϸe14׫bb 0 f~Ff0 Ih]co:ʵ450 ͷue8zU}1pn/5N%I蛎rEaU9-}zƍ7UfɏGܒk>;\xaU+|߬\wL5oe?oтЌg_se㥦n^ߖq KnSo+)l^{˞kŮF8;hoSܤZQnƃv ?={1mjsƝ\я~|owZѳƀi;EQCq#ׯZѳR|,D6u?q-]55In=?~}p{5 &\:SV̀rǦBy)_۵盧-ɑf.k& ilBY\oo%פ"9ZzgM$Sy|eчK"7*O3lr_r__}?Jt<_&EUDl\̛n4:Qr³p^XJ c`%ޚ%e)IoxU6gϔ Lܝ:,6~ػ3|1*Uo'ʗ˒!? z%B4Mf.LyeON,|V8fyrVP{ZLu Խп^z|n(`+KӾǣf}j{߲s^L({7re\Ud#tX߮+2gw}rgqF?G<+(uÖuZ8j ^Oak~]z}MRW ~ݜD 'n!saܾ?Uv3 4)Tkgyo|-ά>L˟O?_;&(`]i-L׶O7qY^Z.p׎j/}0 |w^zy|Ƽww7fW)n7vJ@ 04a}197>Xk4P4c,ꃭa=_~)+ײj}6WuI[r+5[|Ӯ9)7}{(NHŦf}Ũe\7Ef/tL-Lgv|fL]p}olwɦMJԊIR?J`i !ι m\z伻/ HC.8O9Wݱ$G+"0wrasN?H2.9~()-yw\,t/6wZa2/I-&H.P$tw'oMX9.ek6IϿO%{VZXnFO8K=c4?a՜iqfiyXO14Ц_s#>F19<+,4>3~7XK _ U\G˿|?.F3{g埽rmeXݙ#κtBŗ+4c*]eմz]=Y6q64c2y zB&grh: <-c-tk.9"]&, U/YrtS B675wg\siT:9~/2|SE<-,@rycҨ 3%g#Nk(ky6M8ꬫOκzhU6{/*Ŏ"|OMV}M^^egHcW/5uꔩS.|`Kf1! #G4, CPN ҼQEzp'w`Һ[}ޞ.c 7^ZqV1N`/Fomui7 @Y!͢kHDq}06KB ɴ #C3ꏾcWeb'\>uĦ K%j|U>%$@Y;-){1"T@ ݈ UBHǬ{J_@ETXǞ[^XUi7?q`k/dqH4_*M[eܜ+bS\YR"䡉餧(H:kJ *Gyv0*5~^<37NnR2-U)B,JEi}PhJ vkL%Rz4oXPoY#Hθ%Io¸o< >>Ɨ-SkY\~)?ԧa^2^ӹosK[O}a0f0X[+,S_Lalv_ӦO ?'ԗD˞UdƛG/0駫ngCz<=\]̾/TΚnQBbsle߼'yąDkCwqZ$go/ϺcM=[둲#]7őysWZGn_94L1snT*|"W0 ׏#5(y 6씭j׏}qȄKQmO(-M%CZl3LpQ;g5ko_Yevb5vKwX:]X2w)gNa=^(:o⧫Θy&#t̍wMxO=?-tQi/^Z}s.x~_ǻrp$waç͘>s&]LQIgm~FkY\kE>u9Wfmc{ߍSJs,wo-l-/]s,~_96?Ҭ:9_V)2F_ݸI~?O?|>6m(q=TUM6p٤fvkgOL K҄H,IK[}Wfn`4{cNvJ*ߟ#~' dr6_þҴś_wN=ʋ8ǧ1 ;Q9˧_u7K?6Js^%#[*A?ǵwJ xaۋKN_x}{cq|慆_&] v?~j,}oz ;ߜqVf$֎5N& ݂N>{l B8`~X Mo{yS<,Ϝ5U7.qTm|U5N6 ݂p~' 柵x[U+.Ɲ\!\myl&!D&Y ٫ڰ_V | dX= sQAsI$F9@j/(WaD^·V #0Vl€6K1~jfj@,G nAj~S-z“cs/hlO "*JqHJI+?0M M9Wi+99e'3j+JTJUYIZuvU$3$8qqEG9ARDϟЭ)U.ڏ)Ct 4 P OQ' ?N=0鸯%}^׿6MvBhd9roAH?(ƣ) k0-IU)[9Ĵ\'G(tlv%Oطxg~sڛ7}ڲUmi=p9 LsvLH]e0,h& Ǟ}M sW.mgUt굏޽0Ez7kF\?$hKALYz ]_ɕݺ㗗dUڼ+}Q\W?Y_Uk)Ӷ{.,-=v+i2/rX+Ca}x3ߚ%k;׽$|⣛::T׻]kRk/j}0׾"J[ N:f G?g_J-)Q^䵲9zJt*ym*詈ӨWGt?D(g>r{}[ey}o^$홥AuSŚ[vt|/5i/oT7k\G'|]㞍K<ߗ?7?)?Y ?~򳎹/_F4rDVV=3 -]-12JǏ~aAFL^W_{m FwbuV?2MsDŽ%av~8"ɕ"E\iF}ce^+I[%g~0$,ۖH{o9aUا@:~ΙaVb{Ή)IYZaŻ]dR [=meWHPbM&x"a}qK|7jN}{gGSJQR/|*Q6Ȅ0^$ݧ[JnTg]KkOi C! Cft|_zGS !# $Im9M9˙ɬM( MbӳDUOdY7Ɲ1kyF&*LID36jŪ6rw?/5{"JƨF&N1.{o}󪺱8z’ko͒9Ksms\Y)f57EV)yofddBOpl~x$:>s{ !wdS^{;G4}UZhi3{E-ZFۅ .7쓕{, 䎇Ji۽~}vUu$v +VGT1Κ97( 0~e\{_.ٴ%Oطd~oИ}ue#g @n8({Nyiq};W&9O7%Y0mϒj7&J9g|w{[EKyIZCʐȇ.w' ū]߈\?>8ǿ1O/}ֶ ͈sMSD.ik:﹯Wv凷'z3U^y{+\ s:~}e-Oc{rKװ7=}v='eS^O_+#k `>[#}5kV{Cf K'6GcҸ3(ܢ9:,۩3_oKgÊOnPUvDiS.WJIӴ ;gϞVVܸ΅wm6һ/Z0syݻ&{@k8ⱶ;:;rTuZ2ղZxʬ3O]|퓟Wgwq&#uy՟< >@0l (Z7s3փ+{~z](mZ=1\v׫%ݾޥ#Gϋ0-jtO}o(_˜}"8V;\C B҈1g-Icڰ쾋̞=ssZZnټN9{E?sl|»60ֺoq3\pWk.L &DIW=~c?op9w=t3gys+7qc 9swm2e._ O=E׾̌18\g^ԙg/EFKs<%KfvWCŗL buEwp|3{Y3f޵`|,O5c]{;w~;Kpެ3ϸ♕f ;6nk>k:eY/}lyQs$6 fΚ1{pc`=!yzNJ;_~_//_?~WʎW_}#op~Q5Z\|*З7^~J~XoN QkcŪvGG-oy.O 79em<\s|G̎_L˲hkW<|jc[(v|Dןy%ϏS{Ꮝ{s<~77nxsIbUI#׭?Qw1*˺5y퟿s5G>>_׭_Ȅ+L|S 0X'kP0rW#GϜ+7KpRo_ZDkk I;< F`d-YApKD Rݐ^veQ"Dj01F؋8//T{g7f^} UV9/*Ƚs>G94;΋ c KH6͇|>z?ߒ{d/>N׾hV$N]rÝWΈlc\~ˢ,-C1 ؋8oLaN<{٣#Hӯ1tR~rYs_9;G+9 W޸<,ԏM{7ˁuܣov IF7\na@s3}ɇ|Rթ9o_MTwY]=-Ev#ʞ1ch7K2$%k̇TwY=7/aX|{#H)$^5?nׁJQ*gw|/l, />H|Hut & ,Ec>KB {J,4fzbho~YΉ0~87ҊY 6X~/9zݰ,c9}?;i6;;2J+o+!NL ql!`KHeTWћB'RzWe y?}8m>D \~Irz=&E [BNUtIT?4(#eބߚ\P|F8 {Uaի!c*ZJ*LRii9[cƽo.  9v5}rNrV~h5g_5Ec|f8ӻX,HF2ʸ),N?ɴ,~xg"|(?>ؘPSZuݯ>]ڮٲб1B$ J.-pc~\#&6ӻZ;x: ߦC^Ã_h^.rD$h5 #ԉzE Vioo?dXj0kv@߽~=[z;*eĄ͛t\jutdT||\&ֳoZ5D2@~d u7ޏ_REGEɸ7 p=O &BLqp>T Z"DFFLfn[T*XL _{Lѷ4mW? |a/!B&J%/3B?sox9 {N5F\Cw7` mnh .M5m2e 41PXu xgTUVfNNIE'&P(;v 7 ~9 [C\@4Mwvtfd475&'Q-kX{KȘc#X]U6&܇FMY5$ Fi5 x1P[֕{L+~(41+j_YWhJ 1dH[]]CCûwU&J_+>Z#;:P9=MAНE $AWog3*+ȅxY.wV5(Ɛ8i8@Ts7Pպɳ'h@_")9Z{np(< ˙>1Ju :ZDpcvfVG+@*sm= DB6{R)C{9ִiRt24KSѾªv "uC7m2eVrcb Di%deK=>c\^ڳmm;Th2&nR9Շ Re1(`ixi!N0YkwJ)= O qt 6N{b8gw]ήrڔHu(bіi*S}j8,X_݀&u|VZzQSR$%++k 8]k)Z)Z#1 #&̈N*"375qt(rUT$j$p߯@SG;r ITN~Vl6R4: }nFxc= d!ZIRF.m. SCbq1tWѮ"kb$5*h4/qX7 D`?kQP4gƂ(vW4huC j {zxR'LՈݻTELOSNTw.n U߽H5+GK ;vLUr߮E9!$];cϞ%MMz:$' cj:@v̌h)Af)#*@}xnM B X^YBcU90vzAaTO>q_ zD1v3%8!QQ79#+leV,^D2[UN9wsD=>pGqUM- '#$)㡰dfZLΈЩvHeZvV\X6II!@2:nK#%$I eԚmuz:4ĵIf64$)' ^N=fR*C`Z*3')D dHRĤ1͓sw4u6{TNՂ%:4UyI*"tl䫀%%/B1 }1)&NK;>)Xf*J@c[qO8#LgѾ*"s)JkݖWU%0`s6R*B 0@ fz,T_|*P8fR"`|tЀ/~{;q$4S.#HG:ې:!pITx^hd{W*IX) 6oS[ڰ:im#eB`$ eMo Fh''4޵K0 Ud6$`c7; վ{NסrM%iY-cⅻ6-"*}زÅ[wہ%eh^Cv]De_9EjFD=i P᮪^zk(e}E&FԒ@(Ƥ*&1^Ŗ WA MwۂIihb2VoTۡ-H\@[+* ()bd՛['BA:Z'V#<1ybH}7rqKӑ1 B=7#Нw1OƊ>͏VR*TEPVMVH$02> <\26%45V6HS3CCSЎ;q4f+ ռퟪS'1Av08t\ڢP(u]*Uꖖ `ȩgf꜔ޯ°a) 2GOH8?RL73i\O,:gztN@FOvɥaϗ YrD͙vִ)qE;и 'ztΚ=S]8?ROQGBUlF~V@j2sܿ $@B=f2:}v?)@ch_}|̀=J˖w~YQ!MSn᧟3#a ׷uvM.N*yqs0aچ?;~3N?dkGDڢP* <(hhmK$nXY!n>KHHŒH%X&JDSX _1pBBC ݍPTJ81 h]m!4$3 67 FM"J{+#+=qG(|Od_ڏH׆wP%HEbB bD̏oI y"A"CV1#=+8VI#6Ãlj 﫡۳"+xȟw`ӷI-81Lb94e۹ B?1b-y,0xC*n4j%RqafYEP('9dEQf%>1hnkJ'#jk@ x~|U\\LX :Nj#CFҨWuPϙ}_tqnE CBCCCCNLZvlN]qbn| Y^ьlFF@:rlZ'_$hJMhij}hhԎw? $贜xe [ Opq⣺:'77%5M*&$rsG|O6 X,fhƿiX ø1B&.c7WڿR3-MvCIs#Z1Hjٱ&yV>P|BB"P(ʄĺc0۱uN<9-&?Ó5uf)TrLȴZ!`KsɁ3 "Ulnٱ*yZRƽbC&̝A q Uɳ'F@ˎ $eKucc$m=rAo*&=7't!Psxq"e 02y?a`,GKjZ v@"eD|jzJZvlN +r;,1r RLtz??7*T&8} (Β?Χ)_`(S{UA&Cl}LZPwrFn(>(˚2!B ֮ 9w=|҂60ۧSMkǎQ]"'Ŗ '@_u`ߞb,- tWɞk|)Ԁ1`]m~noTP6{01u5VWuf⾝DO 󙽧6phAŋjܲg[u ѥ ƾ`Ҏ=4|d4 I@RAO"M&D) I p?+(=ШΝ)fXpK TYZ ! :#"}m 4!Q+& {3k!'m`ez1uXIɑ Q!Twt dܩвsc&ARbCHZ43?B8RdHiX|vss3M#RG$iL-k;41)9Y *sZvn,SKZj }7*rUq-{\V Շ+UNԐ!*NE e[.P E1hv r0x[c;ecoyyrTʜ09Yb 5 hSsΣ%QacB$PU)` \Br8m#0V @JDei> vt $Mť6BEXlZfjaE*R2bBVﯴHȋӈ:qTbN uuM"F򘴤 zJ@[&H!Q'd{HYRC*!#a>Mnݗ<+Xcƍ41)M _JZ0ąE@$EUe0;sB*CvC{]U6{Ttv&Ƈ)HNEuQyC=DHݞB7 Uaª1IjWb=E5a *UUu29 Hah]6D!RuADYab}&+8~efZ*Ap$NGbOY!v@TZQhGΈn$R ȅt8+l yW@sS%`~<;`9s`t=_;wWy O4&Ba񎿻@BӳPŇ4ݻaZCguE=6= B0[FV6s\{7AN` @hǥ9kC1MH]݀1`3X-Hao/EQ &:A! 2 )!f:BNc6ؘpD16?*.;/6), qv[I0@U1WW.P &1{s4).Xnlsf?qmŕ 6BuzTZZُ.DL:e l)aԼTؤ":kbt=XV2!wJBGZR0fZ˜ˌ>5Zc@ PMw)bc#R1wb4"`ӛPǣ8{DP@I6;N tD2qZ) ]t tq!r2{ %uTh a8) -/?Ѷw[m”qalc: 0@\_l]@P),\[E"ѾB4Z`~&#a瞞 U|dWEnRȎ! ܵ4cq> uJnJ[iBaх3OwPKR=). $IʝnQ^$&M|^a(pUw^ (DǼc~VAG4b0{ cff\&M۵s^㒫R##b2)x\0o.~4 9Ym00lyT[LFT`61b ??$*BjZ,EGfh2TTHR߃wڼ-ٳ2CKD"ijWi, I ӎ1ld2T*f'0tXY39*L:ҴZA*+?@&` c#btK9GXx=сl@$MLut|mH$h&9K<A`~qH$|= .˜ά*Ἥ!W /)Bi_dfhD @hIL9B MOM0D-6im\B) I#} !@)Ʊ_O-&]GHq I/q2c܊!A,6K\B`9I1u55@31ca-(Wѱцnh`0`BBBCB=ZwmI>!|0&mxA04W3AwqvN'iKsd2"#;tj}e=պ{ÁIR OkI%A)Og2*5;J_{͛ TANC; ?XdjwrH1/C3q? c!n,@7m|SVCWSuў]qjȰYa]76 =:-GLJS:JFϙ@R8m|/uC@$("xYy-"X3ؽ5q|Πu uHkf',-GK: ) OJ9 17-bIhrNn;:7 ͛5>\@늏5v;[ɌU^goə:!Ʊj?24B8yM% ΢DhkcR::;CuJ) սpv^]bANPE;Ub$,< ƙn'xEI t03m7ƎQSjsc遃ʩ!`siIssMH͟5ƵYOPBw͑S35Kp{q!*EAPF,vE֮z$D}4?+Y9eb&R(]Gk +_f7=^ #hckW'A_Q5:1 BH(0vHErzZR cB$"65~OS7ED|^R(S8L#Mbf\&CF횅&1%fkII/iIuw" Aӟ=kR L͈H+jM PJk&13agLiʝEQ9PҦM(HcLӴn罒 PH෯ i ɰ m"-JPfn?PSfZ*ZY maqBD,YLbWeUnn+EBƎȁ⺘ݺ{N\p. >KqCY(a/ -SiG K=bZB( 2":F*\_ fhm1> }q{Hc}7XJdˈ#⣾8,tRJڻ-Lsq||RB qFE,@ZVܠY )N:#$TGHLTL?k8kZo})ػ-[ 6'#SscN4!)_՞*dmzP4MLJRlhoo;P*&d2&ǰ?'d4h^ᅬx~d21O"Vv'I]ebl u:L&h1b1EӔsH$ )$,\ӫTNvl=EQ]|i}Euyu ?NQ"]Vee)N7OP$/+RSRG(*(?ym#b ;E"""e2= M&s[K`t%>?Yv# q/ (vn.BdrY||\]}];ܽcR+<՘w)C\Vh~Ve34MwrysSWF!**KץR{v>toK>}nu)`Ѕ/ )/㦅24@m{/ BHVDp̕P**JTm ̀lchbl #ƱgЦ,9UA Ocvd}w/OǽC ;IҬnT&H%^LooPG(F|89Ovcеd-NؽfsL;N!lBS.IR{,.8o.>ղ勷?P];»<;K_?X@r%|F˔q`L۾- }-]1)+R]Is_-6HrFOmc/?#Vt gV̺#M>zŏR9Vn=M7|r$^/YRsN˞(ӼYd5b\ K{?#G1sgUz`9lp{xPm2#Rh9{;1t)!h|Ao'$ EO_qɂnX2Z+jG/~d?U,wϺ~Qoh z IT)s8/Іj BX "hCscs]1fэ׌j" SE1Jgm@z4 [2wrlW hzAQTyE\vt=+*{~myðؓnz\({h$RS=3g~{[0߳NQ9c {dMHj=zpJJ2sg_\9y~+7aW#۳Z8۝NQ4MkZD%l꺺H-{\6@HD*dO6nWLJ-vܭV(HDr8.2Ά!A,K|b`hk\$-J#j'  4juGg׺egDFslim9rD(֨e%m=+p} tnE`@0ms]cHv65/m`͘ic7}I'/2&,6 ɥqq ۷nwwsES)12KV3/#`Q8{V N>%#թӫjMk{Uie}$R$$%HmߎIcϏLwM!"oZJT-ƆL@UoEvFF%af+T*Q*Ռ/#`lsa3;csu)Zuu!(q\ 7- c>5+ CC':ÙX;,0@NDնoˑ.-4Ɔ cCIQy22yTFmRJL&w0ZQ\eaHYXx$o;E;m a ϶FFJIDAT肨/;cZU+bfTDde(n ᅶ}۫qҰx ȬuHИ0;.r&3r&+!ENCiCJϴ nh&IlR*yu"7-Uƌ2NuGOJS@1c T"1T.1DdO&,e)&:MHqLQc%,m4F& J-N9vRuU(&HvZ& J=r!t|(2W]1Sb`;zVrY˿uc(@{)p#>s;:!SB҄H{[6nܲpEA#LiCc"PK7i0!OJR  -8.-.DDYLrXl2u T&>5uSa-J0)MH RO!t*X PvUWtwc;*!=NJin9? yz|߱9>8$aTHc7K? CYia\"ZocB(h&~~ R^;`$F'VƆ cOC\أa*!c ȜuEY(As8V^ JA'X3%ر<"!ٌ{鸏 8LmXHUB (BFm U&T6ulݲ!4X@U urTiωnC]|tg. ej74H̽˲ yT]_^3]_WVOF=E(bqmiMb0c3Uwq7}He\ X\SD+=e1Vl c7T4!Q "* "!T]uf&Hƃ1gVH}ER )DgPFnM1RTZrx[1*sGՔjjҔd@F%(jJ}3Hypg!ɹ=^@UW*ytrI4! 4~Ia2FN $;ڔ NVT$r$ MQhR4vFSQFV dJBD*tN)]>]"c@o*dzt~Ff+ eqYzSkۚwC<Aw'!)N(WxYuT*bсBT'k;+ f|z6K++D$)1BhŘYm4JuB7 ~BPIBQwtvn_̰p=YZ[Z 2ZF1CBHVĢpdDP*~O#;' ! @ I pr:t&9)9;'9\T&&&U7g`EJ_{m4BX0X-'OZ9vH%J5,wҁ:ih lk޻ =G@;17T e+.,S'ȇ8vEx,]q֓֗iRLWG7)OQCWVS\60,,lB~>$H"\ciE8HBcce 䤵AgaYXBfFZVUXea@(JHOU8Y"SwT+cfZ,.HQT]eKrM E-&DY|M}E򈤬h 2WW6tZi 茬P`,-%z+$efǫ83@60mP ej3fՇjř3"C}sikk;R/'Nڲy޽{Lc%F AX#Ϳ9i0h 0,Jc:RjȚ&,G) 24Z_Q&I͛*E6}S]А-2M12UK#u]V"bsF_y5fb G+hmª 1`+.<\)/HQtWqQ*1g(GKUYB+tGLQ6]Y}Ά>rDn&H%5.'MIp_J}%h"3GgDI2X L &NeI&X |"Jw c_pr Zwh6s\1BlNVIq [ FbDr:.U @X#!OLR 42JӦ K$c ˉU:.5q%vMHI|#*.5H!Q&9y}AIX\\v#ScP 2I8Nˉ޾gzP%MȷlGeo\)Qb$h1-抃{X'1ƍhX.Ϙ9duO< s<NC14!WocLj; T2[M {TGP@JɊs%?#nscXYwm :-m7L[mU4tWiIMFcB0`LcX#J@c4{HZ%!) *u6̌N`RۛPM3H ʫi1) ueVhn}[Q|ؚ$'zGM&JCtafee4sv6mΝa23Gy3x.1EQf4ZD"cXtN vp' UWy90R@HIJѪ#[1!Ħ LsFzD$@ *UIT%f%QEhyY @ʢd]U%NTPjRA P&fU8$!3+x^w(>>&OCh5 ڤûiA =%'J4讹F?~ݻv1q⤝;wLi'-FfVxV2E56!dnm\$-J#% H~9@߬"$or ij2gǻ%)y)=$RLKyL$ObP&CSLJ&)c"STvPСc-諅侲(FOtK=:Сn6防O :l?CÌnHsh0MmFv N(d&6dݖ}"CR8] iZӥ54|M`4"""u:Z$=:ȌDCC'G/;'|`҂0qVYӧ:HMujֱqGnxϥ4E5Չ%b{] 8j8l1[hqP@-=uli,,PAN՞ |Ee#F߯ @Ǜ]4Zmwhdt\8Z?/)`з4 Z: $͙p31N4.6T.BLUGv("EM](R.+ H"B(iGsVLVjzּFƥF{VU4, ! LUߪ#!q`wc\~NKR##7%Gػj{&mj(l5R P'fDD`,-եM6kҳ\Au)j@1Yٱ v AD"&: ܇< -JD"()EPRtF$9Zf 5>[MZ tߖ_QqTJ&F M0r>dG!ID"Ɉ!Cfs JlD$12MEvnٺsOau7#R"$TD'E-cB=jLFZB8$6Fbl7Pc Y|bB0BZ,V)ƴb" 1b}vӇ-!]*%#g%ɧMDLRR26%ohjG1)HYxBGIJ__5&=To0vM y@ ܝv#YzL/TǥcH0ežLP&)cv}CEuSo}$ zD꺪õf;NNO!no\ [~{q JGFnJr)+#FLD7Bu`ss̎RAX~ C+K8Y4wbxLah;Z{ 0"H nhm33Q؍6*$یvR$hCuYK 8gG >݅Qpeu*UT,dJ q حCIȈvU* BLM6:D6!vcox EJvX{qQ!ة2(C8fVmÞ0bPcC~}.خ+/n2)L"@[ꫛVv7T70`L3 Bm7TQ.b+ vfیccEдQ[VÒUBS}sbhؗ)(ɕ/H"TCeJfF&A ]Zjj04-+IZ|*|A?RG0~AV8wzc |C*úk*v D)!4J/9XkeHYHܨx9B$MIihDLoO>)PuV/aBJNV !Lm.[H!QG%IWRb奻i"ݽ*ɕ$Hʢua1 <>=`Dt`W8$% ̬` nx@$B 6z[ $ڕJN.DR$Rml6UmULFR[ ! IO;z\HDG%zGMg[B$GeqZ@s)ɑd{rh,:%':C>! O ObOD1A;cL4A\?ix*>J Y' 0Z-k?,HY_[N}峹PYhR* !+D"V'6o 0 "z}dLbX,'[Mch-MM!у`gV`{!d2TgBHU 4M k5TCS:>9-a}AwQ7)[Uli.=RjbT9Ss‡fYtzREl??P @hF! h^ N"I2IwMS{1g>=c;][ݭ]0^% >T qG{^3X,;dϤ {RcL3 C* !$ƴ]TZXR ?.N6V2 ;frrKhFKH/Ns,W7(K i:Lٙ]` c~cG4~!>1 %xe줞RýUuP֬z|?JUuܺ+RpܛvЍUCi:u}5Se@ bT8*53x 2Sf ,3=4i?o`<rVO^OY;4ʤz~DL8:TŲq8vmPmzvXDk~n'/k95eFC'&ȆF3NOdzh!956WuN ]5 x{Wyq~flbxaX|>~]~[2Jz \в aӵ8Xl9ךA,}c9]9^`B*vSW}h鍱G]I)JP_U"֖NoZ$f@6'fL6#@G= uzI*QŒdRL GMNǸg ]e%0F}m ]Msιsc ųbY @=֥.ȶ&ru>ۭPg"_vM=۞tttWEM&!gޚG={;<ՁCy0aL;.X+&+cCpgZKQezA78 Dálljm.ϬD^KQw,е|)?ՁOu 8qx(QkE]|X9c ʅr!rJ!sYg˖OMG%׬y*+yu {D.w?ꘘ;W>vkjsu}վݗVoRWEe` \ "bzUkWŁޞAoBirc[>LIJS*1pdu^ ]c-*'AuBe++%IuzU? pcP2-[yS)Q/^3nfOW^iwͮ@UDZ: FzSЃTrzv2Ĝ.#&'f:酥IG#]JxtfD&g9qkBTR|r<ӹ5yl2sJAГ_%(N+D3eH( ,ʌ6E+:ΊL0WyЊ̆B$)L+#s9X,+T˕tD"nTú8yҨ*D|>SyC@U~f_O]}dw,ζf[y\O_}lJ2j8Pskw`|L $Gˡ>@d%wj55{-ǯ8mnr)-;ͧO_j<3l蝝_~4UsQi69F$KCuƒPA"]MM Y+%0;|) ("tZ])A7:lN_2uƘ 58wtlOorLn;\=p^ {PF@n+֥/gSSϱuy<ܸ'5:*RʹAK >Gmzdʝ*9[˵y`i5Zz\Uc9h^^!Z֪׹rrY;˕X"rYWhWb$:nEA{Z ⁇0 Ml2 8}HVD7*KK6=v 1RKGnڭdF*ݒO<666u\PDG3n9cIb 5`1jkslpS:Vv7Lz<6E$WcC$ C:nxB='5|ppl1RKjq+OO^:j>5ଧϧSm]dbO:6m6/D'&byf8E6ʟJ O6 ܤab#7s^(cmw㼲f׭JOm\9K^97]f EUokP8zV9g8Tx ^rή㨋vGy*W;c䆣'/6xVlD=OY Deb?qj+z1>51NU-]^h Pd -]^XygϏTma83V xz>N.*Rr&0MM]3v~" :d4FRED2ۻ}v}ň!{]~ X9qD`J%KjIX ,mv1RCֻS\88a6xyԧ===(yO 60S`@sp@7,;sm<;qJt=d\82snlPᑡ"FɋB-[:7{_2oYvf|{ļr15iə)p^/O&;#]K I8e30΁CG9c 8Jjzg|oO,˕B82:f6:j'I[ y*m^,P*<^c)g,J&Dh\8I|$ӫT-˅bK5=>9ˬ BZX,1A:xpt!u4DBD%XLOrw,"(&Id<΋emD@%%<|/n tab_.9ry 9{kU+e#:FjKW\dC"Ue9 .tϗsO<X9U[㸥^_`fL,:{:Yi Rl_l1 M*G+m{8[ kE]0 1 "p btbpI’e3X*Q]ןq3l*g1"? ˆnMUv=K+0s4i%2cL00WX]i`sʹx`ao&'*rtCWu(٬B=yk }gW%dfKlȁs@v 6](hs\*W3Ll6+eEXƳEl8π\+lEY<'YŢk h#5Z颵Pb܌e"-}mcWMTvx9_хgB,;wl4ڭ6ޤ*}נ#`2-ͳ/2xv}M&5(H 9XIjZ$Ijll(b1EBq֠#ȞAUUUQ$Y5&WVc,!k4>n1RCnEAcDW\/h#h#5{Z%h#h#;f! AeAsNu3V,VZ-Luc HӣcGObq4dsX,=!'.9vԡ>hѨgp6>:<߷vz#'/N;web񄳻ͼ^^̈́S%6n*GV0[,]õ)Uˏ.m]^FJw2,;ua =vRbD,oj4K;jcMY`?8˩XrD7*{u کڨiʁvӻjnkK\7q4:{sPBCR)+ rzk~ $]dTu4tz-"Tx@4;;<M]%TzD/Zj"i\P]Á@,WDeH%= ;@p|8M1my9 E9 dn65ƆVG"ɬDs]~ ogJF~ @9FjfIR:VEqg&9WhXF=.r&DCfRFL'g2ѹDl D^Zͭ&dz.k?a6ɰO%zzf8P 4A686:4c7>e,NEdilR-nXa>Cn*P"VO4(dA-L$ JTHY]dk:FjƾcVNG&QW]-ꕙDi^dUo'˴B)JVnTpL,m= *Z:W,nDQ^ sRI'ՠ5ZQ78 t9]"i|I6Ә`Z!,e09[i\^'‘dXPj46*XyUlObC);lu "(a5X<}n`d޽@ֳќ'9%eKܩ|"Qs^۴HzQJ.H4^m|4n,PBBh7[̄SOeC<p-c785{$2>6"@J+j:yLtY9KMN9qdee\UY@MM.qӋ++'_}|HؚZLKbb˺zD.Me ޶3ʭ0r?~NemT|-M[B('.-JVo, tuLU Ol[eu4531lq\~1F㸲)%Y$~3r,q&9jK֪ DhYS]G\jG&d(#85}۽9YJk۲tu[T\2|Tv:o]˯Scquqjn5/ezڭXW*.JzCTmlmY R yE˃߀H=LT'||2zIx3mY 8КHݢi٩,˸ol|gqNG&rfZ  Ǥ(Kt6q8k'6rS2ϤeQh*A%1RSp;"ɒ$Q&)O RuzI,ܬ`!A 8 aA++3w 1RCf_OS}ARI J,WBoe ]+vwuY-Z cbCSv|2ynjEޓyr 7'OSFEFrc>\N9V9%i/l3//f©qⷯ 5>r#7+<.L:ۍXF EQ6gD,oj4׋nWU5#5vG*Q a_e?-.l;7QI͆ew%.90{)1TÁ@,hjlio4Rw \+$gᅒDz[MCի&hU6YPTC@< Ж?.2:{:=brfl*Pd Ӯ%V#ü)/H2 ަ2Sy0j2H$8htx|.@oal`vR u1RC8pOs3#d scC3v=U񱄹T"@)9=:7﵋+lX9 YDۏ4dÃ)T5pmhR7p,;;2k=c&z*?!q(hJ֍ /Dgcǡ@!1;abzl&\ZNgaۼ",3Ln$@) m@WWzbM{!(h9pι$m M.!jU*Pk&""9r6A5scKlK-ղ$J(\8NmRQq5{T<8 ˅bmk͛A#W_0!vS(Nsg8nS %ZpFzVm ,h٥t(˕HF[o_\mp \N CSNkuWZ"ՋV3A6v8 W, &" }?i-fB)f @j!cz2JMMSe3sޠP H9[\*ȹ!D/gB \ ˗FtR3tLP9 5|.JtY)??N1,yM,k\za4.o6KWl3h2s瓑x/ۭ ų% \$VV9@)rn>[TQƁiD8΃A׸N֞S9gdwq:Fjc:B6N@tLF/5N__Ij6CfrDC0FS9 B6|O7=(_wK`YFeјjjMM]{bD*[ͭfإn[5i-niǢLT(T2ʍX(+2 Qj똪.9 Pb4_)hj(gcɹc* qeu,sw sh6u?i@1F?E9q@L=M&H.ZHlt]|U. A:yK]1:Fj]31Z8't xN-Tq=Q@q˞GΝp;8"M'm%Yko¸N1RPB8c@8_rt9Jo 5wXmv$ᄩʥRj~~60NV L3)C*1޻_,KB2I-h/t]W Xnjg (HA猡xCdYefBgR t >iTs݄vC9Fj P@P~~AnbQ\YBp7 7ٔ_sz7aQZNVW tۘ:RAc.ZYr=B@|)n dc&TbV8 }1+POcAA.@9F PA14 y|8^ ;kvb(&x ^"9bAmx~I݉"ou)rQVΞԄ -ssG~H/Z[Fw$ǜT& cX&QFzf /IQ!Bl B -MFYRvŰnEQeYyIAdCX5A!A.@9F PAcAC33\.  [_=#S 1"hp!BE<>Tn;2k@9F:BuCOGGX̤R͒$uvN{wݘY1tP(_iL9=:w=<Ⱟ;zdփr TZU$iB>ow8Rb6+=2L;IgАl]]7[n62r X,r]UIŻ{dTnL 匱bƘ1}=*/|!dc +n9Ϫ}CoggMOzd6Aή:&s Ï~yQ~yʏ.FJ _W ؇}O?}Ӆ|zTS׼F6?/;;y>>(=[t'7?{s{wkӁsl+}2ߪ(Rw09&}k^v@Q?o_I^G>7̷{ZӗOٗ,0+mH__}w|&ؼM`kپG?q ssu+^D2я&Ȟf ~m?y捻sowok}Vk{j,ێL5(Rw?ퟏO)07-d?x|jx_┓ݿwsgׯ|G#_oo8=o桻[W4Sμm*ॹ_~ _8o<;o{K@>+Ѳ;?xˋLj+?Cq{H+?O?dgj#ȥ'_ݾi%ss[Za1Gns/r/M۵mضxjb\lpN?/l|24ӅRlv{jb|̞k֤gg~eW18O_x|{H>Ao_':sB62|$٫r]~l>/jӃ>I/ >DmZV=H~=wv ?Yu̶(Rw0η vszیfK+>{e?_imU -B"1+=,Vp8W~煦~ ڔml`V|>n_ U"3wORrO&jbQ PC~~m29ΉjUbpMiHr!j}kpi3|k?2ՠ#Hݱux'ѫ]=G |&q>M$g{^ռU+zy?9 %/?/p R믽Cş}'n֊xqLQ"?Q!ScL?zwd{"Z7q&zy]YU7>{w~t]hy1 e;W4t ^:Vi#5_Ls{lAh(#0.$ltՋ/iپst^~%˘V,hT4H }[JcխZz+</}b$WL淆w<}]K|bo/gWr=#S ZRrZE|30~/xc&~|_~߿uϗ?oqMlC*WLG]|Ӈ{Yx{_yܶSe;+Q^/hufٜz^zȑwP!5_^'Η߉뙿eHK_us_?ˉ;ުLr,巆~ǫqh8[oz[_׾gor}/{UY~u%52Ր~4Mqbym ?*M:s)i9~ӮΎe=yϛ Ï^?h44&`T4h"[slZRwp;?|? RŬV|2"_Ω'{/'[fFb {G< ǯ ' P}zYy55ѝ޲lp*Y uc,dr[v[!O-,f[VE1>,Fv[>OLxr=F9F!MMO4}``qp_H4w{{ۙ9󡐦R4v߾fo~ܸݻJ:}[8ArA>eMb``*.*۪ u _ՀTXg( b1xWw){jeYf6cqȄ It@ZU8(). |f%EQyg9zO[-S+EKj  ,Ri` ̘WUM#ը~¡;xF.g`_;'h|T&6cnܰw^mk͑b+uR~0(2Ťia:Vnff6>iJmzuGGU=7KKs3TWV§AEfDI,#0dĀL8y|Zx ]ؚUb>LQ{yXӲP6Aӎ ]e@x7+kJ?8ADoe1n `yLȢb(ΠI:\BrFSq7U{LƣfXW ^Tjd3fx ]$sY-$#'MQށùٜ3 0TZA[_' lb|6YKD|݄n^O.={,珄 @%xD$|I$QeBQh$,1Gu]Ǽ4HG+Hb4Dt-\YDS$b::H'$c5\쪈X1'J6 )TBp#n"P(*lGXQa,t3.##xY% m˖ڧk_ڊKäJHuzN=M6icp aGsC^ ݔ;.\em|r n#B=+(f7@¨f4mKw?@04MSܷҬ~P kwzδ5[R8ѯ)v|!ZZ vybB)%#@unh8*"njC~5kyaƚAЀˌF[^):8$c` ssr> S?H 0nBTS_?`ޫrxM/̽+qm_rzg vXWY\{,$d,kf;OSyaJC%TGzploNrO}.7k2`‡jܹ+WrޖșɔZt) ē3}=ݶU[uMn3mw?ގo͙]̰S:s\1йb)n^_zUKʔj Ϫ[*ZKtmK<`JޫtYiKfWɘIENDB`kvpm-0.9.10/icons/app/64-apps-kvpm.png0000644000175000017500000001267112770324126017563 0ustar benscottbenscottPNG  IHDR@@iqsRGBbKGD pHYs  tIME2`9IDATx{Uy̞=L&L2rar+ I$ArQ\ǣmss,==BVmmJiU܅ *@@`B21 dZ3$83I 1|ٟ~ky< 8;'pP{5_s>8kA dhxBD \&Ri=S)RZC)==tpPȣA3:p(<&p ARs[NKߕI!ry5kÐC󼺃[r~;y(f}߯9>иSloYqٖHkc| /oyqR38>\sL紫O;ycR>(a3Q#wrz³/:Duz2ZX>Xzljy闞wSr"3kkqڵ8@Nh2`ɚO} .e89ӳ M(|1w@~ɥ}W\xz{N?NvZpƸJM~ Ƌ182X، !w @]u'xYΜypa CԖw%;n/ :lJp t嵟VG;c{M8|0]w{^MS}826x7X3pQkyg!R.29w8wu@͌իtڷNZ͔ZsKaX[l!G֕O=㡅f1aX91 ydRdEK׵NmtB)8wxɎ[MGF>8/>iZG%(xq|ғV-d<1~JXw8&1v-"81H!pΔ2SSbVLr':D&%"P$X* qlsrPB%Jgjhiir#,>\vل>Zk]dB([S[|~S #X4,G|ss Ŕ+o|`ݑH'3uyN.Wu129sk ֍pad3 n|)y Щ{6sϽwF\-u"SijVeR R`ZzhBI 5ZjҤd"<4Pң&fW \օ%dq9LlD2q`f% (Rh 5J LO`U[KH@}m8㔓@+Ny(Z!U %TT*ah<3qF6ISGNV.@ 3BD{QHBkO{Ac> ĆWX}{7PŨbz\WzxHl""m&Dj)%RDF<iQiƟ,>k#-<l1XpBf!ضMI`%}I‘Lh6ruu# ێGhҺ[a RUi+ 3IAI8 kv'g?NԾu/ lWO_u<;BP0n:F,uQtpVP ?BNGRb`4e~.~_Lh 6-|u{EPqRv_<9.vۉ nb, 6ɷd0Tm4ڤ~m&}I@? soH?Cn}wxj3)?ܲkOW0~*٣y.đKc/3uƔb"MjIo$Ì>Ǎc~ϼIEDA̮.nuW_{՝n3ƭɾB2^OW=s?amXpդd=]@ua) 2891e߮Zi6aI$ShoXF8'hnʓ̚=\Q͍_Psտw~܅īNݤy_?OYt: J1&."Y5vtP屚OCkf5CGFlq\m$c =dT*OSc=u1s]|7kKq֥o冋 S.~oo_*?o"/n߻p=P8.?ſlvGW T S(Jyo"!<5(dIj΂1f='DGGY0D+>#29ڧ6j޳bL-P(6w?ȭK=⫟/?AwtJ}ߞ=ۊ}ݝ5./μ/>K]sXnJAp$t e%)ۯ^f%+28B d5%36Y$g %ʼV3{J6=,]M 9aA6`b[Ƕ^ m⸹M/Vb#66z6o0=?W?'/Z\<ſ0A&W~?fg~֮x wxb N]Mw 4sw>m1[i̴<[j@Ho뗷ӻahji|ccG6Bzh}aY=}žO]fϒٹPD}9'+c?ﶤ񵛦;z2ݬ\|B5g:^i櫟=͛.Mo',LL&|SK`~g)rBwyqgG}x.z-w4ؔM{G/kֻbحvKoz}^շIf6OhfMkgl:h7EvB -d=5/w+՜Ʋ7kaIENDB`kvpm-0.9.10/icons/app/16-apps-kvpm.png0000644000175000017500000000165612770324126017561 0ustar benscottbenscottPNG  IHDRasRGBbKGD pHYs  tIME6f].IDAT8OS[hWLvg/&lŸ AM%hU" ">(BK-ŴBR$E}CҾXVCT[^fggfwgֶtsLOf` DR(@4PJ'O'Xz]]ou,r\wղ ^` &B0_)5 !" }}tO3xoO ghrBn*}/l´m=r++vѨ84=GSo_t)AnAm.gq,  ^Cf#dܺQ!d!8s¡^[F B(Ղy(J,۫j<~Z3LZU4 a:+l:&KV8_yZ\%#+'A]{^P!H)Xzn|SE出d3nVQ&`b* DALaIEBNb7jqmbZkeվ3}[waxg'蕓 9dmv h-_GPqFIENDB`kvpm-0.9.10/icons/local/0000755000175000017500000000000012770324126015213 5ustar benscottbenscottkvpm-0.9.10/icons/local/16-status-exclamation.png0000644000175000017500000000127512770324126021777 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<OIDAT8˥Ka SAoc6.PDC Dբ@Zxh1N"CN:vf}󶰾6uvyy1=!4ИvCv$ HS8d*P. M`+Sذnv~cHVr}ފOKW55Bb `l`yj`yk1AjE @EoqS2FE64(l=$6#1![?@Dw$-WfW&D$Fn>SRJuZNWc#di@% b_s Rۆ^t&:?!DmSQeJWeJÈqMT 'DB:RE_as3ȯC2Vz9W[9ŢwU*B4!B|zPJ fVcdEmZVw!Opřzp!MuS>x9f0Uއx8GHv=}uʡGBy=-Ka J8K+${?`vLЉ37ӿѯLjIENDB`kvpm-0.9.10/icons/local/CMakeLists.txt0000644000175000017500000000123012770324126017747 0ustar benscottbenscottecm_install_icons(ICONS 16-actions-add.png 16-actions-arrow_divide.png 16-actions-arrow_join.png 16-actions-arrow_refresh.png 16-actions-bullet_star.png 16-actions-camera_add.png 16-actions-cross.png 16-actions-delete.png 16-actions-error_go.png 16-actions-lightning_add.png 16-actions-lightning_delete.png 16-actions-lorry.png 16-actions-page_white_add.png 16-actions-page_white_camera.png 16-actions-resultset_last.png 16-actions-wrench.png 16-status-exclamation.png 16-status-lightbulb.png 16-status-lightbulb_off.png DESTINATION ${DATA_INSTALL_DIR}/kvpm/icons THEME hicolor ) kvpm-0.9.10/icons/local/16-actions-bullet_star.png0000644000175000017500000000051312770324126022122 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8c?%aRӷsnNdogOd?moq=KQk|@nN" HFzݦr23 p=hJ hB?nf&* ~~{ެv]_Agcoz@A^9!HŎĤL +PeIENDB`kvpm-0.9.10/icons/local/16-actions-lorry.png0000644000175000017500000000110612770324126020750 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8œKa(F}")UD!6@A BQP.B ZTza@HBi_-u Zn=yfFrz 3LPTSEӧ0ޮc#DaNn)aTwe"a& ԄkbSNtq05LTttfVDw?jS\=?yP[Zm5d M%C׬lAEI+3͇qq K e }[jCtnX́]e$ Mz1(ARIV'%LmݍĵH޵؀*hD]* @vpܝ' JDf_?J/|yQzS]}#t>§mS/>g/3 e@T*a` KE?*{7lߏi?lCIENDB`kvpm-0.9.10/icons/local/16-actions-camera_add.png0000644000175000017500000000144012770324126021642 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥S_HSQ{kst+Rp(?0ꡠ-(z^"꥚`=hO0B(jl,ԩsss۝\Qz+|}0]?KԽrVuZU*(f5McEFyHӛ, E1 _cP\ײc"\z#]]]v˅T*UlllӁ+DOi0ưrY,ː$3u=jD: (p:FBp8HrFN,;.ĵ5466Z9Y g[a/H~\%'& nߊ"Z[[1MEE f5_1dUUA䀹xrvLD@E6U7&dyPUK׵]2P*?W@¬aG! *P!\CnX.VpGZ1[)8~Gb6PyT Rj6$ޝwqdhk3r ~8ĉ?ZC{~ĠϏd|uM&I-..2^ {kĬyPvg)MlLF1o(W.wv?ěE ^) 4]{DJFg3:ٿ>[n Ʌ'㉦:IENDB`kvpm-0.9.10/icons/local/16-actions-add.png0000644000175000017500000000133512770324126020335 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<oIDAT8˥Ka[/Y()%X(olNۖskn.-h;8fEP"jïMGˈ}yພ羹$I.tulu AX:𼂒ZHh1DnZJOJB{Z?`2`S=N$ő=;a &jw qJG#<"N2h8޵`6xցn_+ ~Zto}`x%XЛ͈ hXѿƻ/}BJ_G&|Qr-6Aރ EL⬡\U3:WUh[C6+ 6.f *K͸ܝFq ou4܄?d|XҥMvD` *_[ #A20liR|xq`4w=\uQ m+G|%$5Թ5RO*YGMUO Gqj4ְ(X& s1c˭(LVf RdjQ '-1ATA>U j4,pV"4L$e@.ArBY a~myY])Q8tNLܞt2"I o=CSd)__AF(IENDB`kvpm-0.9.10/icons/local/16-actions-delete.png0000644000175000017500000000131312770324126021043 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<]IDAT8˥KSa[nQP2wܦγL[,biaA\Cv_2MlZFjסNMjmkʷ`&.#z<ϓ bVPT3%I{GqRivȅ tz#E6EddJ`DR2<]N ;4Ѿ;m>78ɀQe6LIt殷cq!z |v j/Xi@ %1|hl !|! Y#uUNw]˼ H3u t]E>k%IfoRD:0`~ | (r on3oG0!$V *[W0_-+ dW&2ZfMFVJpiF&B > Rg- ~ CmڴER ឫ p5ްy+21Kawh` #aZ񽞆TZoLѓ`"(?'ˎJvKކ|:G9[aw82 Jw f'ymzsӘTsw__ιIrIENDB`kvpm-0.9.10/icons/local/16-actions-arrow_join.png0000644000175000017500000000116212770324126021754 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥RKq~ fkr[[ I.˓%];'"ک? F}+XMwV)Q}yep|'87>;z{.-E$n Ǻ:Fz- {@%3J𺽠}tp ?dw6֊ҏٱWZy#ʣT\73'|nG ' 1qTLL<{(hu K%$ZS[wH޿;A["D`G 93G(o l&޻pR!k _ϋDvG] \(bJ(}4UcA j"%\|k g璙l@0V;HJRһ]C)NT}*ʼn\Ά@@(ZC(BNɗK][v@ YDbF"$aM\3~TZ4r>~'Kg>IENDB`kvpm-0.9.10/icons/local/16-status-lightbulb_off.png0000644000175000017500000000127412770324126022300 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<NIDAT8˕OPƽ?tW{rf좭V.nb˙EZ+Դ2 KKL$OTDDRAD^< ,9;{s%rό-|r}ˉdjvLz\.7j+1cIDHsօ6s5vFH&7iP*<4Ҏ]v&RXŚHg32)x$ ӝy@ݸݹP|kkDwt#<@7=ōC ˎ+,AIA&fze#1Ыwƍ/*Za2hJf,vHpmwq; Юrb8^)qE~G' y5P ~QСҠsP)jD3oMI:߳eo?7Z a 9/ZE_"4( ^~k5u?n*ME'1'jx*;nPv%T/j es U-V6RF]}=xebE+==XB %U'eKK%cG$՗ A (>hEIENDB`kvpm-0.9.10/icons/local/16-actions-lightning_delete.png0000644000175000017500000000135112770324126023110 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<{IDAT8˥SKHTQ;w&s,u6:**Z$d""m$*hodJ1zZ#|̨=)::sN:8Ms$!g)m)*88Y3n"n`lCtkp kIv9WM2#+=m3:own? }v$s%y{GE, ȹ)EK<#Y(=JQÃXpધai)HUɪ{U|npYP<Zt'SVs-_)[uUCWGְJ B%M0t48KC/uvԚ: oi3l}Dm {,y mp,46V^-)M@oޮSՊZHYycQFEM#}ėwXpb͖1Ep#4 ߅@r";{!u g%DIڵ*C806Bb{/Pe KJ9v(e-SC<)}OD;$S/`H \9Ew@| B yڎ;^+x߼ڋZyx^+_7FIENDB`kvpm-0.9.10/icons/local/16-actions-page_white_camera.png0000644000175000017500000000122012770324126023222 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<"IDAT8ˍ=ZA߹*Ic4)$A, kf!Xw&&**qר\?W3G909`y()LQܑn' jZ6ncATUEP/OJ%b18N8DQ P5GnGXD B(_-W|@لjE\bt<(~|}ܝBXqVx!! R<eɡf÷.0f9.D.d,]]@r4v邛S=P4 zO48J ܢZJ!'rrg}鬪;2QTh4Ղs^WV# K@NSj3^f<H5sƎ *'麾1]PsVY9]<@IENDB`kvpm-0.9.10/icons/local/16-actions-page_white_add.png0000644000175000017500000000100012770324126022516 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˕KP[8V"Apj[JS,889U`(ZRǜKSӷpr/wk`PkRYeUѣ+J^7 >,4E(R.C&A<X,I ,#P.QVJ dY$ (JKthD |dkf&6:Fk96J*Ȳ)=0t:q A#Z&r!.Bo8 ذwٛ4_q`ST»ʼn-_Z?^D(z }=~H@ a\a0m4e5eE0<#̕"&`AGya{4)l}O/!&RIENDB`kvpm-0.9.10/icons/local/16-status-lightbulb.png0000644000175000017500000000141612770324126021444 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˕KSq}ћHoLL܋"솒!PRtR&$^22p31RiSy7񶔍m^v6n9~.ZZ;MIENDB`kvpm-0.9.10/icons/local/16-actions-lightning_add.png0000644000175000017500000000135212770324126022377 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<|IDAT8˥RKSa>¶!QL!yQZE&VPQ#N헔]Xh%5'HDs in͹휭Cv/<>ϗ ò1gȆw`a[<_=po>ת1_ bK//>Gt ~:7X, \)ê|pBĮT%;"Řgn5Eƈ0t9\d)q 3ļ-Ok:Mp*xpie&YራD.Bc fҽXZ0g,"Ƽ  9y$;5{yGw?׵Om;7)H@11mN/qa YIU*m>~]#2R=E EUZMA4xܣ/[W{.-kygY NvtK0יrn>ƶh\$ibb*OwAW&vvԌDXA]%0 IENDB`kvpm-0.9.10/icons/local/16-actions-cross.png0000644000175000017500000000121712770324126020735 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<!IDAT8˕NQɉϠVȑB[( &^M6M| Dްҙv:官J-%Nd LB>w_3:*WrlNC/-좕B'{ u_a46ҽbߡE%D47;ٻƩ;8ˣ}>6[ӕS@*Z Qk>~͵hB\9uxZvYb J Cيٽ?BYvn&kft$,d9Zap\^ Y7 QJF 9=Q4 ؜Io SBpsI) Fv(@yՎވc\@ %% Z2h'@d(<|áaJuM@O⤁LGjd!X8Af 5J i K->w62ƾWH}:mP]XB0QX=ib_g=!Ftt…clrIENDB`kvpm-0.9.10/icons/local/16-actions-arrow_refresh.png0000644000175000017500000000125512770324126022456 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<?IDAT8ˍKa3bvВBB9d-Od6_t䡁eŚܡIͱaIF%he a#; ]8axo<+ ߺqeZ!;"4U/(/Dv]toUq|͸U!\vtPָ#i)8:S.άj[/Uv loL3 k&~j~MLGJf2Bʔ̘Zf=EfQd֟wLۅȌ#fd&ʝulՓ;xLH֌ ÉȠȠ~+:#hh#5=&N.ehiL(B:* ƑCr.)8UȝвZCHJP cBB,A avJ\]_@HY0s P^Z~uwP*@%fR)3X2RCͤ TTb oރQXPALMOB9:q|sf$ٺ|[R > m9>X{.ڱ.7ob1x&kx>2}K.b`V}fU(4/`CC#Jo ]IENDB`kvpm-0.9.10/icons/local/16-actions-resultset_last.png0000644000175000017500000000101412770324126022654 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8œkQ7; *,E/A^TI%XX,,?H6ɨA QL&q>X&_q81)%v]j1W٫G ;E)}z>wOu{G%lj٘=*l__v$>lIENDB`kvpm-0.9.10/icons/local/16-actions-wrench.png0000644000175000017500000000114212770324126021067 0ustar benscottbenscottPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˕oQ_[c+W.]хi5  g mJ[Fq#_)qո;,@{;upLTʙH$(Z XMɤni"\nt:}yD 0 \ ږIU4(r HMk̈_4_ziy'"[ n1rM_A`b=$Ik_p-qS~=li~3Bv"qZAԧ̸r[G]<&e!'ڸ67 yq$OX!=_~1Gs~EZQx&qWK3!ޤunkzGrjQnIENDB`