summaryrefslogtreecommitdiffstats
path: root/modules/fs/path.inc
blob: 714578f9b3cd798fc5de88039fdf3fd5bff1b6ce (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
#!/bin/ash

DM_STATE_FILE="/run/openslx/dmsetup.state"
readonly DM_STATE_FILE

# Return the device corresponding to the given path, or
# empty if it's not mounted from a device (tmpfs, cifs, ...)
fs_path_getdev() {
	local _dev
	_dev="$( df -P "$1" | awk '$1 ~ "^/dev/" {print $1}' )"
	[ -n "$_dev" ] || return 1
	[ -b "$_dev" ] || return 1
	printf "%s" "$_dev"
}

# Return base mount point of given path
# eg. call with "/mnt/nfs/temp/data", returns "/mnt/nfs"
fs_path_getmountpoint() {
	local _mp
	_mp="$( df -P "$1" | awk '$6 ~ "^/" {print $6}' )"
	[ -n "$_mp" ] || return 1
	[ -d "$_mp" ] || return 1
	printf "%s" "$_mp"
}

# Get fstype of given directory
fs_path_gettype() {
	local _mp
	_mp="$( df -P "$1" | awk '$6 ~ "^/" {print $6}' )"
	[ -n "$_mp" ] || return 1
	[ -d "$_mp" ] || return 1
	awk '$2 == "'"$_mp"'" {print $3}' '/proc/mounts'
}

# Returns true if the given path is the root of a mount point
# Does not normalize!
fs_path_ismountpoint() {
	grep -Fq " $1 " /proc/mounts
}

# Helper to check whether given directory resides in RAM, either
# by being mounted as tmpfs or not mounted at all in which case
# we assume the same. Returns 0 if so, 1 if otherwise backed,
# 2 when errors occur.
fs_path_isvolatile() {
	[ -z "$1" ] && return 2
	local _dev _mp
	_dev="$(fs_path_getdev "$1")"
	if [ -z "$_dev" ]; then
		# not backed by a device in /dev, check for network shares
		local _mtype="$(fs_path_gettype "$1")"
		if [ -n "$_mtype" ] && regex_imatch "$_mtype" '^(nfs.?|cifs)$'; then
			return 1
		fi
		# Neither backed by a device nor a cifs/nfs share, assume tmpfs
		return 0
	fi
	[ "$_dev" = "${_dev#/dev/zram}" ] || return 0 # zram is in RAM, like tmpfs
	[ "$_dev" != "${_dev#/dev/mapper/}" ] || return 1 # Doesn't start with /dev/mapper, assume some kind of storage
	[ -s "$DM_STATE_FILE" ] || return 2 # No point in continuing
	_mp="$(fs_path_getmountpoint "$1")"
	[ -z "$_mp" ] && return 2
	# it is a device mapper device, check if it was setup in dracut's initramfs.
	grep -qE "^$( regex_escape "${_dev}" )\s+$( regex_escape "${_mp}" )\s+type=0" "$DM_STATE_FILE"
}

# Return free/total space at given path, in KiB.
# If the given path is backed by a thin pool, snapshot, etc.
# it returns a conservative estimate, considering the remaining
# space of the backing device.
# Returns "<free> <total>"
fs_path_space() {
	[ -z "$1" ] && return 1
	local _free _total _dev _type _id _name _bs _tmp _tused _tfree _ttotal
	_type="$( fs_path_gettype "$1" )"
	[ -z "$_type" ] && return 1
	_free="$( df -P "$1" | awk '$6 ~ "^/" {print $4 " " $2}' )"
	case "$_type" in # EASY!
		tmpfs|nfs*|cifs)
		printf "%s" "$_free"
		return
	esac
	_dev="$( fs_path_getdev "$1" )"
	if [ "$_dev" = "${_dev#/dev/mapper/}" ]; then # does *NOT* start with /dev/mapper, assume not device mapped
		# In case we start using aufs again, check if it needs special handling...
		printf "%s" "$_free"
		return
	fi
	# Split, since we might need to do Min()
	_total="${_free#* }"
	_free="${_free% *}"
	_type="$( dmsetup status "$_dev" | awk '{print $3}' )"
	_tfree=0
	_ttotal=0
	case "$_type" in
		snapshot)
			# Field 4 is used/total in sectors of 512 byte, so divide by two for KiB
			_tmp="$( dmsetup status "$_dev" | awk '{split($4, a, "/"); print a[1] " " a[2]}' )"
			_tused="${_tmp% *}"
			_ttotal="${_tmp#* }"
			_tfree=$( echo "($_ttotal - $_tused) / 2" | bc )
			_ttotal=$( echo "$_ttotal / 2" | bc )
			;;
		thin)
			# Determine matching pool
			_id="$( dmsetup table "$_dev" | awk '{print $4}' )"
			if [ -n "$_id" ]; then
				_name="$( cat "/sys/dev/block/${_id}/dm/name" )"
				if [ -n "$_name" ]; then
					# Get data block size (in sectors of 512 byte)
					_bs="$( dmsetup table "$_name" | awk '{print $6}' )"
					if [ "$_bs" -gt 0 ]; then
						# Field 6 is used/total (in data blocks), so transform into KiB
						_tmp="$( dmsetup status "$_name" | awk '{split($6, a, "/"); print a[1] " " a[2]}' )"
						_tused="${_tmp% *}"
						_ttotal="${_tmp#* }"
						_tfree=$( echo "(($_ttotal - $_tused) / 2) * $_bs" | bc )
						_ttotal=$( echo "($_ttotal / 2) * $_bs" | bc )
					fi
				fi
			fi
			;;
	esac
	if [ "$_tfree" -ge 0 ] && [ "$_ttotal" -gt 0 ]; then
		[ "$_tfree" -lt "$_free" ] && _free="$_tfree"
		[ "$_ttotal" -lt "$_total" ] && _total="$_ttotal"
	fi
	printf "%s" "$_free $_total"
}