LCOV - code coverage report
Current view: top level - pkg/storaged/partitions - partition.jsx Coverage Total Hit
Test: cockpit Lines: 95.5 % 179 171
Test Date: 2026-07-17 07:33:01

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2023 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6          113 : import cockpit from "cockpit";
       7          113 : import React from "react";
       8              : import client from "../client";
       9              : 
      10              : import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
      11              : import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
      12              : import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
      13              : 
      14              : import { StorageButton, StorageLink } from "../storage-controls.jsx";
      15              : import {
      16              :     dialog_open,
      17              :     SelectOne, TextInput,
      18              :     init_teardown_usage, BlockingMessage, TeardownMessage
      19              : } from "../dialog.jsx";
      20              : import { block_name, fmt_size, get_active_usage, teardown_active_usage, reload_systemd } from "../utils.js";
      21              : import { check_unused_space, get_resize_info, free_space_after_part, grow_dialog, shrink_dialog } from "../block/resize.jsx";
      22              : import { StorageCard, StorageDescription, new_card, navigate_away_from_card } from "../pages.jsx";
      23              : 
      24          113 : const _ = cockpit.gettext;
      25              : 
      26            5 : export function delete_partition(block, card) {
      27            5 :     const block_part = client.blocks_part[block.path];
      28            5 :     const name = block_name(block);
      29            5 :     const usage = get_active_usage(client, block.path, _("delete"));
      30              : 
      31            0 :     if (usage.Blocking) {
      32            0 :         dialog_open({
      33            0 :             Title: cockpit.format(_("$0 is in use"), name),
      34            0 :             Body: BlockingMessage(usage)
      35            0 :         });
      36            0 :         return;
      37            0 :     }
      38              : 
      39            5 :     dialog_open({
      40            5 :         Title: cockpit.format(_("Permanently delete $0?"), name),
      41            5 :         Teardown: TeardownMessage(usage),
      42            5 :         Action: {
      43            5 :             Danger: _("Deleting a partition will delete all data in it."),
      44            5 :             Title: _("Delete"),
      45            4 :             action: async function () {
      46            4 :                 await teardown_active_usage(client, usage);
      47            4 :                 await block_part.Delete({ 'tear-down': { t: 'b', v: true } });
      48            4 :                 await reload_systemd();
      49            4 :                 navigate_away_from_card(card);
      50            4 :             }
      51            5 :         },
      52            5 :         Inits: [
      53            5 :             init_teardown_usage(client, usage)
      54            5 :         ]
      55            5 :     });
      56            5 : }
      57              : 
      58          104 : export function make_partition_card(next, block) {
      59          104 :     const block_part = client.blocks_part[block.path];
      60          104 :     const unused_space_warning = check_unused_space(block.path);
      61          104 :     const unused_space = !!unused_space_warning;
      62          104 :     let { info, shrink_excuse, grow_excuse } = get_resize_info(client, block, unused_space);
      63              : 
      64          104 :     if (!unused_space_warning && !grow_excuse && free_space_after_part(client, block_part) == 0) {
      65          104 :         grow_excuse = _("No free space after this partition");
      66          104 :     }
      67              : 
      68          104 :     const type = block_part.Type.replace(/^0x/, "").toLowerCase();
      69          104 :     let type_extra;
      70          104 :     if (type == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" || type == "ef")
      71          102 :         type_extra = _("EFI system partition");
      72          104 :     else if (type == "21686148-6449-6e6f-744e-656564454649")
      73          102 :         type_extra = _("BIOS boot partition");
      74              : 
      75          104 :     const card = new_card({
      76          104 :         title: _("Partition"),
      77          104 :         type_extra,
      78          104 :         next,
      79          104 :         page_block: block,
      80          104 :         for_summary: true,
      81          104 :         has_warning: !!unused_space_warning,
      82          104 :         component: PartitionCard,
      83          104 :         props: { block, unused_space_warning, resize_info: info },
      84          104 :         actions: [
      85          104 :             (!unused_space &&
      86          104 :              {
      87          104 :                  title: _("Shrink"),
      88            1 :                  action: () => shrink_dialog(client, block_part, info),
      89          104 :                  excuse: shrink_excuse,
      90          104 :              }),
      91          104 :             (!unused_space &&
      92          104 :              {
      93          104 :                  title: _("Grow"),
      94            1 :                  action: () => grow_dialog(client, block_part, info),
      95          104 :                  excuse: grow_excuse,
      96          104 :              }),
      97          104 :             {
      98          104 :                 title: _("Delete"),
      99            4 :                 action: () => delete_partition(block, card),
     100          104 :                 danger: true,
     101          104 :             },
     102          104 :         ],
     103          104 :     });
     104          104 :     return card;
     105          104 : }
     106              : 
     107          113 : const gpt_type_names = {
     108          113 :     "21686148-6449-6e6f-744e-656564454649": _("BIOS boot partition"),
     109          113 :     "c12a7328-f81f-11d2-ba4b-00a0c93ec93b": _("EFI system partition"),
     110          113 :     "9e1a2d38-c612-4316-aa26-8b49521e5a8b": _("PowerPC PReP boot partition"),
     111          113 :     "0657fd6d-a4ab-43c4-84e5-0933c84b4f4f": _("Linux swap space"),
     112          113 :     "0fc63daf-8483-4772-8e79-3d69d8477de4": _("Linux filesystem data"),
     113          113 :     "e6d6d379-f507-44c2-a23c-238f2a3df928": _("Logical Volume Manager partition"),
     114          113 : };
     115              : 
     116          113 : const mbr_type_names = {
     117          113 :     ef: _("EFI system partition"),
     118          113 :     82: _("Linux swap space"),
     119          113 :     83: _("Linux filesystem data"),
     120          113 :     "8e": _("Logical Volume Manager partition"),
     121          113 : };
     122              : 
     123            9 : const PartitionCard = ({ card, block, unused_space_warning, resize_info }) => {
     124            9 :     const block_part = client.blocks_part[block.path];
     125            9 :     const unused_space = !!unused_space_warning;
     126            9 :     const block_ptable = client.blocks_ptable[block_part.Table];
     127            1 :     const type_names = block_ptable?.Type == "dos" ? mbr_type_names : gpt_type_names;
     128            9 :     const type = block_part.Type.replace(/^0x/, "").toLowerCase();
     129              : 
     130            1 :     function shrink_to_fit() {
     131            1 :         return shrink_dialog(client, block_part, resize_info, true);
     132            1 :     }
     133              : 
     134            1 :     function grow_to_fit() {
     135            1 :         return grow_dialog(client, block_part, resize_info, true);
     136            1 :     }
     137              : 
     138            4 :     function set_type_dialog() {
     139            4 :         const choices = Object.keys(type_names).map(k => ({ value: k, title: type_names[k] }));
     140            4 :         choices.push({ title: _("Custom"), value: "custom" });
     141              : 
     142            1 :         function validate_type(val) {
     143            1 :             if (block_ptable.Type == "dos") {
     144            1 :                 const hex_rx = /^[a-fA-F0-9]{2}$/;
     145            1 :                 if (!hex_rx.test(val))
     146            1 :                     return _("Type must contain exactly two hexadecimal characters (0 to 9, A to F).");
     147            1 :             } else {
     148              :                 /* We let people use any 128 bit value as a UUID, even
     149              :                    those that are not defined by RFC 4122. This is
     150              :                    what the rest of the storage stack does as well,
     151              :                    and the "BIOS boot" UUID is in fact invalid
     152              :                    according to RFC 4122.
     153              : 
     154              :                    But we do insist that the dashes are in the right
     155              :                    place, because UDisks2 does as well.
     156              :                 */
     157            1 :                 const uuid_rx_1 = /^[a-fA-F0-9-]*$/;
     158            1 :                 if (!uuid_rx_1.test(val))
     159            1 :                     return _("Type can only contain the characters 0 to 9, A to F, and \"-\".");
     160            1 :                 const uuid_rx_2 = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
     161            1 :                 if (!uuid_rx_2.test(val))
     162            1 :                     return _("Type must be of the form NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN.");
     163            1 :             }
     164            1 :         }
     165              : 
     166            4 :         dialog_open({
     167            4 :             Title: cockpit.format(_("Set partition type of $0"), block_name(block)),
     168            4 :             Fields: [
     169            4 :                 SelectOne("type", _("Type"),
     170            4 :                           {
     171            0 :                               value: (type_names[type] ? type : "custom"),
     172            4 :                               choices,
     173            4 :                           }),
     174            4 :                 TextInput("custom", _("Custom type"),
     175            4 :                           {
     176            4 :                               value: type,
     177            4 :                               validate: validate_type,
     178            4 :                               visible: vals => vals.type == "custom",
     179            4 :                           }),
     180            4 :             ],
     181            4 :             Action: {
     182            2 :                 Danger: !client.in_anaconda_mode() && _("Changing partition types might prevent the system from booting."),
     183            4 :                 Title: _("Save"),
     184            4 :                 action: async function (vals) {
     185            1 :                     let t = vals.type == "custom" ? vals.custom : vals.type;
     186            4 :                     if (block_ptable?.Type == "dos")
     187            1 :                         t = "0x" + t;
     188            4 :                     await block_part.SetType(t, { });
     189            4 :                 }
     190            4 :             },
     191            4 :         });
     192            4 :     }
     193              : 
     194            9 :     return (
     195            9 :         <StorageCard card={card}
     196            9 :                      alert={unused_space &&
     197            1 :                      <Alert variant="warning" isInline
     198            1 :                                    title={_("This partition is not completely used by its content.")}>
     199            1 :                          {cockpit.format(_("Partition size is $0. Content size is $1."),
     200            1 :                                          fmt_size(unused_space_warning.volume_size),
     201            1 :                                          fmt_size(unused_space_warning.content_size))}
     202            1 :                          <div className='storage-alert-actions'>
     203            1 :                              <StorageButton onClick={shrink_to_fit}>
     204            1 :                                  {_("Shrink partition")}
     205            1 :                              </StorageButton>
     206            1 :                              <StorageButton onClick={grow_to_fit}>
     207            1 :                                  {_("Grow content")}
     208            1 :                              </StorageButton>
     209            1 :                          </div>
     210            1 :                      </Alert>}>
     211            9 :             <CardBody>
     212            9 :                 <DescriptionList className="pf-m-horizontal-on-sm">
     213            8 :                     <StorageDescription title={_("Name")} value={block_part.Name || "-"} />
     214            9 :                     <StorageDescription title={_("UUID")} value={block_part.UUID} />
     215            9 :                     <StorageDescription title={_("Type")}
     216            2 :                                         value={type_names[type] || type}
     217            9 :                                         action={<StorageLink onClick={set_type_dialog}>
     218            9 :                                             {_("edit")}
     219            9 :                                         </StorageLink>} />
     220            9 :                     { !unused_space &&
     221            9 :                     <StorageDescription title={_("Size")} value={fmt_size(block_part.Size)} />
     222              :                     }
     223            9 :                 </DescriptionList>
     224            9 :             </CardBody>
     225            9 :         </StorageCard>);
     226            9 : };
        

Generated by: LCOV version 2.0-1