summaryrefslogtreecommitdiffstats
path: root/builder/hd-boot/hooks/partitioner.sh
blob: 21e12a94af37cbd7e85bc91c6dba81ad166d2f20 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
#!/bin/sh
# Copyright (c) 2013 - OpenSLX GmbH
#
# This program is free software distributed under the GPL version 2.
# See http://openslx.org/COPYING
#
# If you have any feedback please consult http://openslx.org/feedback and
# send your feedback to feedback@openslx.org
#
# General information about OpenSLX can be found under http://openslx.org
#
# Local hard disk autodetection script for OpenSLX linux stateless clients,
# detecting swap and special partitions
#############################################################################


#############################################################################
# Requirements:

. "/usr/lib/hd-boot/common-lib.sh"
. "/usr/lib/rebash/core.sh"
core.import utils

set PERC_AVAILABLE_SPACE    # Rate to use of the free space
set MIN_SIZE_IN_BYTES
set DISK_LIST=
set DISK
set SIZE
set START_SEC
set END_SEC
set SLX_PART_ID
set PART_NUM
set LAST_ERR
set DISK_PART
set FS_TYPE

FS_TYPE=ext4

if [ -f "/etc/$CONFIG_FILE_NAME" ]; then
    . "/etc/$CONFIG_FILE_NAME"
else
    die "Config file could no be fould at /etc/$CONFIG_FILE_NAME"
fi

get_size_of_parts() {
	local device=$1

    lsblk --noheadings --raw --bytes --output SIZE "$device" | awk '{if(NR>1)print}' |
    (size=0; s=; while read s
    do
        size=$(echo "$size+$s" | bc)
    done && echo $size)
}

get_disk_list2() {
    local min_size=$1 use_max=$2
    local list lst 
	local device ro rv disk_size
	local size_parts space_avail

    lsblk --nodeps --noheadings --raw --paths --bytes --output KNAME,TYPE,RO,RM,SIZE |
	while read device ty ro rv disk_size
	do
        # if is type of disk
		if [ "$ty" == "disk" -a $ro -eq 0 ]; then
			size_parts=$(get_size_of_parts "$device")
			if [ $disk_size -gt $size_parts ]; then
                space_avail=$(echo "$disk_size-$size_parts" | bc)
                space_avail=$(printf %.f $(echo "scale=2; $space_avail * ($use_max/100)" | bc))
                if [ $space_avail -ge $min_size ]; then
                    #TODO: list of disks available
    				echo "$device $space_avail $rv"
                fi
			fi
		fi
	done
}

# Param
#   $1 - device to be queried.
#
# Return: three fields space separeted in sectors unit.
# <start sector> <end sector> <size>
get_avail_space() {
    local space ret="0 0 0"

    if space=$(parted --script --machine "$1" unit s print free);  then
        #TODO: In case there is a gap and a second gap that could be used, the second gap would be missed,
        #      so must interact in all list of empty space
        ret=$(echo "$space" | grep -Eo '^[0-9]+:[0-9]+s:[0-9]+s:[0-9]+s.*free;$' | \
            tr -d 's' | awk -F ':' '{if( $4 > 2048 ) print $2" "$3" "$4}')
        [ -z "$ret" ] && ret="0 0 0"
    fi

    echo $ret
}

get_disk_list() {
    local min_size_sec use_max=$2

    lsblk --nodeps --noheadings --raw --paths --bytes --output KNAME,TYPE,RO,RM,LOG-SEC |
	while read device ty ro rv logsec
	do
        # if is type of disk
		if [ "$ty" == "disk" -a $ro -eq 0 ]; then
            buff=$(get_avail_space "$device")
            space_avail=$(echo "$buff" | cut -d ' ' -f3)
            if [ $space_avail -gt 0 ]; then
                # TODO: interact in the list of avaliable space
                # Get space according to the user requirement,
                # buff could get more than one item, see get_avail_space().
                space_avail=$(printf %.f $(echo "scale=2; $space_avail * ($use_max/100)" | bc))
                if [ $use_max -lt 100 ]; then
                    start_sec=$(echo "$buff" | cut -d' ' -f1)
                    end_sec=$(echo "$start_sec+$space_avail" | bc)
                    buff="$start_sec $end_sec `echo "scale=0; $end_sec-$start_sec" | bc`"
                fi

                min_size_sec=$(echo "scale=0; $1/$logsec" | bc)
                if [ $space_avail -ge $min_size_sec ]; then
                    echo "$device $space_avail `echo $buff | awk '{print $1" "$2}'` $rv"
                fi
            fi
		fi
	done
}

# Default partition table will be gpt
default_part_table() {
    local device=$1

    # Sanity check, leave clean part table
    # Clean up part table gpt, also MBR
    sgdisk --zap-all "$device" &> /dev/null
    sgdisk --randomize-guids "$device" &> /dev/null
    [[ $? -ne 0 ]] && perror "Partition table could not be created."

    return $?
}

create_gpt_part()
{
    local device=$1 part_num=$2 start_sec=$3 end_sec=$4

    info "Creating partition at $device start=$start_sec end=$end_sec"
    sgdisk --new=${part_num}:$start_sec:$end_sec "$device" &> /dev/null
    [[ $? -ne 0 ]] && perror "Partition could not be created." && return $?

    sgdisk --change-name=${part_num}:${SLX_GPT_PART_NAME_CODE} "$device" &> /dev/null
    [[ $? -ne 0 ]] && perror "Name of partition could not be set." && \
    sgdisk --delete=$part_num &> /dev/null && return $?

    sgdisk --type-code=${part_num}:${SLX_PART_ID} "$device" &> /dev/null
    [[ $? -ne 0 ]] && perror "Type code of partition could not be set." && \
    sgdisk --delete=$part_num &> /dev/null && return $?

    info "Creating file system, ext4 at device '"${device}${part_num}"'."
    mkfs.ext4 -F "${device}${part_num}"
    # TODO: threat errors
}

create_dos_part()
{
    local device=$1 part_num=$2 start_sec=$3 end_sec=$4
    local count dif mod

    # Partition must be in a boundary of 2048 sectors (1048576 Bytes)
    [ $start_sec -lt 2048 ] && start_sec=2048
    
    # make end of partition align at 2048 sectors boundary.
    mod=$(echo "scale=0; $end_sec % 2048" | bc)
    if [ $mod -ne 0 ]; then
        # align
        end_sec=$(echo "$end_sec - $mod" | bc)
        # sanity check, it must be aligned.
        mod=$(echo "scale=0; $end_sec % 2048" | bc)
        [ $mod -ne 0 ] && die "Bug: dos partition is not aligned in 2048s boundary."
    fi  

    info "Creating partition at $device start=$start_sec end=$end_sec"
    if ! parted --script --machine --align=optimal "$device" unit s mkpart primary "$start_sec" "$end_sec"; then
        emergency_shell "Partition could not be created."
    fi
    
    # probe of new partition, so block device will be created.
    partx --add --nr "$part_num" "$device" &> /dev/null

    # change id of partition.
    if ! sfdisk --change-id "$device" "$part_num" "$SLX_PART_ID"; then
        die  "Partition id could not be assigned."
    fi

    info "Creating file system, ext4 at device ${device}${part_num}."
    if ! mkfs --type "$FS_TYPE" -F "${device}${part_num}"; then
        die "File system could not be created."
    fi
}

set_iface_to_cmdline()
{
    local tmp

    tmp="$(cat /usr/share/grub/default/grub | grep -w 'GRUB_CMDLINE_LINUX=')"
    [ -n "$tmp" ] || return 1

    mac=$(cat /tmp/net.ifaces)
    [ -n "$mac" ] || return 1

    _head="${tmp#*=\"}"
    _head="${_head%\"}"
    _tail="ifname=$(cat /tmp/net.bootdev):$mac"
    sed -r -i "s#GRUB_CMDLINE_LINUX=[\"].*[\"]#GRUB_CMDLINE_LINUX=\"$_head $_tail\"#" /usr/share/grub/default/grub
}

install_boot_loader()
{
    local dev="$1" dir_root="$2"

    info "Installing grub, this may take some time, BE PATIENT! ..."
    if ! grub-install --recheck --target=i386-pc --boot-directory="${dir_root}/boot/" "$dev"; then
        die "Failed instalation of boot loader."
    fi

    [ ! -d "/boot" ] && mkdir "/boot"
    if ! mount --bind "${dir_root}/boot" "/boot"; then
        die "Could not bind directory \"${dir_root}/boot\" at \"/boot\""
    fi

    info "Setting iface to the cmdline..."
    set_iface_to_cmdline

    info "Generating boot loader configuration..."
    if ! grub-mkconfig -o "/boot/grub/grub.cfg"; then
        die "Failed generating grub configuration."
    fi
    umount "/boot"

}

install_system()
{
    local dev=$1 inst_dir=

    info "Installing base system..."
    inst_dir=$(get_root_dir)
    cache_dir=$(get_cache_dir)
    if ! mkdir -p -- "$inst_dir"; then
        die "Mount point at \"$inst_dir\" could no be created."
    fi
    if ! mount -o rw -t "$FS_TYPE" "$dev" "$inst_dir"; then
        die "Could not be mount \"$dev\" at \"$inst_dir\"."
    fi

    # create boot dir in hd.
    if ! mkdir -- "${inst_dir}/boot"; then
        die "\"${inst_dir}/boot\" could not be created."
    fi
    # create cache directory
    if ! mkdir -- "$cache_dir"; then
        die "\"$cache_dir\" could not be created."
    fi

    info "Installing kernel..."
    # Fetch kernel and initramfs.
    if ! wget -T 5 -nv "http://${SLX_SERVER}/${SLX_HDD_SETUP}/${SLX_KERNEL}" \
        -O "${inst_dir}/boot/${SLX_KERNEL}"; then
        die "Could no fetch kernel from: "\
            "http://${SLX_SERVER}/${SLX_HDD_SETUP}/${SLX_KERNEL}"
    fi
    info "Installing initrd..."
    if ! wget -T 5 -nv "http://${SLX_SERVER}/${SLX_HDD_SETUP}/${SLX_INITRAMFS}" \
        -O "${inst_dir}/boot/${SLX_INITRAMFS}"; then
        die "Could no fetch initramfs from: "\
            "http://${SLX_SERVER}/${SLX_HDD_SETUP}/${SLX_INITRAMFS}"
    fi

    # install in the MBR of device, not in partition.
    install_boot_loader "${dev%%[0-9]*}" "$inst_dir"

    # umount instalation directory, next hook mount will do
    # in this manner we avoid checks in mount, since this will
    # happen just once.
    umount "$inst_dir"
    info "Done with the base system."

    export HDD_INSTALATION="$inst_dir"
    export ROOT_HDD_BWLEHRPOOL="$dev"
}


# ____________________________MAIN__________________________________________
# Check if we've got a partition
# This var will be exported by get_root_part.sh script.
if [ -z "$ROOT_HDD_BWLEHRPOOL" ]; then
    PERC_AVAILABLE_SPACE=100
    MIN_SIZE_IN_BYTES=$(echo "scale=0; $SLX_PART_MIN_SIZE * 2^30" | bc)

    info "Getting list of disks..."
    DISK_LIST=$(get_disk_list "$MIN_SIZE_IN_BYTES" "$PERC_AVAILABLE_SPACE")
    # If there is no a disk with minimum requirements, pop error and exit.
    if [ "x$DISK_LIST" == "x" ]; then
        perror "Could not be found a disk unpartitioned with at least requirements of $SLX_PART_MIN_SIZE GB. Leave a disk unpartitioned with the least requirements."
        emergency_shell -n "error" "no free space has been found."
    fi

    #TODO: if more than one disk has been found, give a choice to the user.

    DISK=$(echo $DISK_LIST | awk '{print $1}')
    SIZE=$(echo $DISK_LIST | awk '{print $2}')
    START_SEC=$(echo $DISK_LIST | awk '{print $3}')
    END_SEC=$(echo $DISK_LIST | awk '{print $4}')

    info "Getting type of partition table..."
    PTTYPE=$(blkid -s PTTYPE -o value "$DISK")
    LAST_ERR=$?
    # If there is no partition table default to gpt
    if [ -z "$PTTYPE" ]; then
        if [ $LAST_ERR -eq 2 ]; then
            # If type of partition table can not be found default to gpt.
            default_part_table "$DISK"
            # query again for the partition table, it should have a gpt partition table.
            PTTYPE=$(blkid -s PTTYPE -o value "$DISK")
            [[ $? -ne 0 ]] && perror "Fatal error setting default gpt partition table. Reboot the system."
            # If we created a new partition table; so this will be the first partion.
            PART_NUM=1
        else
            perror "Fatal error occured, tool to detect gpt table returned: $LAST_ERR."
        fi
    else
        # find out number of the last partition.
        PART_NUM=$(sgdisk --print "$DISK" | grep -E '^[ ,\t]+[0-9]*' | tail -1 | awk '{print $1}')
        ((PART_NUM++))
    fi

    DISK_PART="${DISK}${PART_NUM}"

    case "$PTTYPE" in
        gpt) create_gpt_part "$DISK" "$PART_NUM" "$START_SEC" "$END_SEC" ;;
        dos) create_dos_part "$DISK" "$PART_NUM" "$START_SEC" "$END_SEC" ;;
        *)   emergency_shell "Fatal, parition table could not be parsed." ;;
    esac


    # Mount and install the system
    install_system "${DISK}${PART_NUM}"
fi

unset PERC_AVAILABLE_SPACE    # Rate to use of the free space
unset MIN_SIZE_IN_BYTES
unset DISK_LIST
unset DISK
unset SIZE
unset START_SEC
unset END_SEC
unset SLX_PART_ID
unset PART_NUM
unset LAST_ERR
unset DISK_PART
unset FS_TYPE