summaryrefslogtreecommitdiff
path: root/common/quota
blob: 4c1d3dcd7dad9cefd9f2599d9b11e9ef5df9d7c8 (plain)
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
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
##/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2000-2001,2005 Silicon Graphics, Inc.  All Rights Reserved.
#
# Functions useful for quota tests

# checks that the generic quota support in the kernel is enabled
# and that we have valid quota user tools installed.
#
_require_quota()
{
    [ -n "$QUOTA_PROG" ] || _notrun "Quota user tools not installed"

    case $FSTYP in
    ext2|ext3|ext4|ext4dev|f2fs|reiserfs)
	if [ ! -d /proc/sys/fs/quota ]; then
	    _notrun "Installed kernel does not support quotas"
	fi
	;;
    gfs2|ocfs2|bcachefs)
	;;
    xfs)
	if [ ! -f /proc/fs/xfs/xqmstat ]; then
	    _notrun "Installed kernel does not support XFS quotas"
        fi
	if [ "$USE_EXTERNAL" = yes -a ! -z "$TEST_RTDEV" ]; then
	    _notrun "Quotas not supported on realtime test device"
	fi
	if [ "$USE_EXTERNAL" = yes -a ! -z "$SCRATCH_RTDEV" ]; then
	    _notrun "Quotas not supported on realtime scratch device"
	fi
	;;
    *)
	_notrun "disk quotas not supported by this filesystem type: $FSTYP"
	;;
    esac
}

#
# checks that the XFS quota support in the kernel is enabled
# and that we have valid quota user tools installed.
#
_require_xfs_quota()
{
    $here/src/feature -q $TEST_DEV
    [ $? -ne 0 ] && _notrun "Installed kernel does not support XFS quota"
    if [ "$USE_EXTERNAL" = yes -a ! -z "$TEST_RTDEV" ]; then
	_notrun "Quotas not supported on realtime test device"
    fi
    if [ "$USE_EXTERNAL" = yes -a ! -z "$SCRATCH_RTDEV" ]; then
	_notrun "Quotas not supported on realtime scratch device"
    fi
    [ -n "$XFS_QUOTA_PROG" ] || _notrun "XFS quota user tools not installed"
}

# Check that a mounted fs has a particular type of quota accounting turned on.
#
# The first argument must be the data device of a mounted fs.  It must not be
# the actual mountpath.
#
# The second argument is the quota type ('usrquota', 'grpquota', 'prjquota',
# 'any', or 'all').
_xfs_quota_acct_enabled()
{
	local dev="$1"
	local qtype="$2"
	local f_args=()
	local any=

	case "$qtype" in
	"usrquota"|"uquota")	f_args=("-U");;
	"grpquota"|"gquota")	f_args=("-G");;
	"prjquota"|"pquota")	f_args=("-P");;
	"all")			f_args=("-U" "-G" "-P");;
	"any")			f_args=("-U" "-G" "-P"); any=1;;
	*)			echo "$qtype: Unknown quota type."; return 1;;
	esac

	if [ "$any" = "1" ]; then
		for arg in "$f_args"; do
			$here/src/feature "$arg" "$dev" && return 0
		done
		return 1
	fi

	$here/src/feature "${f_args[@]}" "$dev"
}

# Require that a mounted fs has a particular type of quota turned on.  This
# takes the same arguments as _xfs_quota_acct_enabled.  If the third argument is
# '-u' (or is empty and dev is $SCRATCH_DEV) the fs will be unmounted on
# failure.
_require_xfs_quota_acct_enabled()
{
	local dev="$1"
	local qtype="$2"
	local umount="$3"
	local fsname="$dev"

	_xfs_quota_acct_enabled "$dev" "$qtype" "$qmode" && return 0

	if [ -z "$umount" ] && [ "$dev" = "$SCRATCH_DEV" ]; then
		umount="-u"
	fi
	test "$umount" = "-u" && umount "$dev" &>/dev/null

	case "$dev" in
	"$TEST_DEV")	fsname="test";;
	"$SCRATCH_DEV")	fsname="scratch";;
	esac

	case "$qtype" in
	"any")		qtype="any quotas";;
	"all")		qtype="all quotas";;
	esac

	_notrun "$qtype: accounting not enabled on $fsname filesystem."
}

#
# checks that xfs_quota can operate on foreign (non-xfs) filesystems
# Skips check on xfs filesystems, old xfs_quota is fine there.
# Appends "-f" to enable foreign behavior on non-xfs filesystems if available.
#
_require_xfs_quota_foreign()
{
	if [ "$FSTYP" != "xfs" ]; then
		$XFS_QUOTA_PROG -f -V &>/dev/null || \
		 _notrun "xfs_quota binary does not support foreign filesystems"
		XFS_QUOTA_PROG="$XFS_QUOTA_PROG -f"
	fi
}

#
# Checks that the project quota support in the kernel is enabled.
# The device must be mounted for detection to work properly.
#
_require_prjquota()
{
    [ -n "$1" ] && _dev="$1" || _dev="$TEST_DEV"
    if [[ "$FSTYP" == ext[234] ]]; then
	dumpe2fs -h $_dev 2>&1 | grep -qw project || \
		_notrun "Project quota not available on this $FSTYP"
    fi
    if [ "$FSTYP" == "f2fs" ]; then
	dump.f2fs $_dev 2>&1 | grep -qw project_quota
	[ $? -ne 0 ] && _notrun "Project quota not enabled in this device $_dev"
	dump.f2fs $_dev 2>&1 | grep -Eqw "quota|quota_ino"
	[ $? -ne 0 ] && _notrun "quota sysfile not enabled in this device $_dev"
	cat /sys/fs/f2fs/features/project_quota | grep -qw supported
	[ $? -ne 0 ] && _notrun "Installed kernel does not support project quotas"
	return
    fi
    $here/src/feature -P $_dev
    [ $? -ne 0 ] && _notrun "Installed kernel does not support project quotas"
    if [ "$USE_EXTERNAL" = yes ]; then
	if [ -n "$TEST_RTDEV" -o -n "$SCRATCH_RTDEV" ]; then
	    _notrun "Project quotas not supported on realtime filesystem"
	fi
    fi
}

#
# Do we have GETNEXTQUOTA?  Querying ID 0 should work.
#
_require_getnextquota()
{
	_require_test_program "test-nextquota"
	$here/src/test-nextquota -i 0 -u -d $SCRATCH_DEV &> $seqres.full || \
		_notrun "No GETNEXTQUOTA support"
}

#
# ext4 (for now) is unique in that we must enable the project quota feature
# prior to mount.  This is a relatively new feature ...
_scratch_enable_pquota()
{
	case $FSTYP in
	ext2|ext3|ext4)
		tune2fs -O quota,project $SCRATCH_DEV >>$seqres.full 2>&1
		_try_scratch_mount >/dev/null 2>&1 \
			|| _notrun "kernel doesn't support project feature on $FSTYP"
		_scratch_unmount
		;;
	f2fs)
		_scratch_mkfs "-O extra_attr -O quota -O project_quota" >> $seqres.full 2>&1
		;;
	esac
}

_require_setquota_project()
{
	setquota --help 2>&1 | \
		grep -q -- "-P, --project[[:space:]]*set limits for project"
	if [ "$?" -ne 0 ];then
		_notrun "setquota doesn't support project quota (-P)"
	fi
}

#
# checks for user nobody in /etc/passwd and /etc/group.
#
_require_nobody()
{
    _cat_passwd | grep -q '^nobody'
    [ $? -ne 0 ] && _notrun "password file does not contain user nobody."

    _cat_group | grep -Eq '^no(body|group)'
    [ $? -ne 0 ] && _notrun "group file does not contain nobody/nogroup."
}

# create a file as a specific user (uid)
# takes filename, id, type (u/g/p), blocksize, blockcount
#
_file_as_id()
{
    [ $# != 5 ] && _fail "broken call to _file_as_id in test $seq"

    parent=`dirname $1`
    if [ $3 = p ]; then
	echo PARENT: $XFS_IO_PROG -r -c "chproj $2" -c "chattr +P" $parent >>$seqres.full
	$XFS_IO_PROG -r -c "chproj $2" -c "chattr +P" $parent >>$seqres.full 2>&1
	magik='$>'	# (irrelevent, above set projid-inherit-on-parent)
    elif [ $3 = u ]; then
	magik='$>'	# perlspeak for effective uid
    elif [ $3 = g ]; then
	magik='$)'	# perlspeak for effective gid
    else
	_notrun "broken type in call to _file_as_id in test $seq"
    fi

    perl <<EOF >>$seqres.full 2>&1
	\$| = 1;
	$magik = $2;
	if ($5 == 0) {
	    print "touch $1";
	    exec "touch $1";
	} else {
	    print "dd if=/dev/zero of=$1 bs=$4 count=$5";
	    exec "dd if=/dev/zero of=$1 bs=$4 count=$5";
	}
EOF
# for debugging the above euid change, try... [need write in cwd]
#	exec "dd if=/dev/zero of=$1 bs=$4 count=$5 >>$seqres.full 2>&1";

    if [ $3 = p ]; then
	echo PARENT: $XFS_IO_PROG -r -c "chproj 0" -c "chattr -P" $parent >>$seqres.full
	$XFS_IO_PROG -r -c "chproj 0" -c "chattr -P" $parent >>$seqres.full 2>&1
    fi
}

_choose_uid()
{
    _cat_passwd | grep '^nobody' | perl -ne '@a = split(/:/); END { printf "id=%d name=%s\n", $a[2],$a[0] }'
}

_choose_gid()
{
    _cat_group | grep -E '^no(body|group)' | perl -ne '@a = split(/:/); END { printf "id=%d name=%s\n", $a[2],$a[0] }'
}

_choose_prid()
{
    if [ "X$projid_file" == "X" ]; then
	projid_file=/etc/projid
    fi
    if [ ! -f $projid_file ]; then
	echo 0
	return
    fi
    perl -ne '@a = split(/:/); END { printf "id=%d name=%s\n", $a[1],$a[0] }' \
	$projid_file
}

_qmount()
{
    _scratch_unmount >/dev/null 2>&1
    _try_scratch_mount || _fail "qmount failed"
    # xfs doesn't need these setups and quotacheck even fails on xfs
    # redirect the output to $seqres.full for debug purpose and ignore results
    if [ "$FSTYP" != "xfs" ]; then
        quotacheck -ug $SCRATCH_MNT >>$seqres.full 2>&1
        quotaon -ug $SCRATCH_MNT >>$seqres.full 2>&1
        # try to turn on project quota if it's supported
        if quotaon --help 2>&1 | grep -q -- '--project'; then
            quotaon --project $SCRATCH_MNT >>$seqres.full 2>&1
        fi
    fi
    chmod ugo+rwx $SCRATCH_MNT
}

#
# Ensures only the given quota mount option is used
#
_qmount_option()
{
	OPTS=$1

	# Replace any user defined quota options
	# with the quota option that we want.
	# Simplest to do this rather than delete existing ones first because
	# of the variety of commas and spaces and multiple -o's
	# that we'd have to cater for. Doesn't matter if we have duplicates.
	# Use "QUOTA" string so that we don't have any substring confusion
	# thanks to "quota" which will match with "uquota" and "gquota" etc.
	export MOUNT_OPTIONS=`echo $MOUNT_OPTIONS \
	| sed   -e 's/uquota/QUOTA/g'      \
		-e 's/usrquota/QUOTA/g'    \
		-e 's/usrjquota=[^, ]*/QUOTA/g' \
		-e 's/gquota/QUOTA/g'      \
		-e 's/grpquota/QUOTA/g'    \
		-e 's/grpjquota=[^, ]*/QUOTA/g' \
		-e 's/\bpquota/QUOTA/g'    \
		-e 's/prjquota/QUOTA/g'    \
		-e 's/quota/QUOTA/g'       \
		-e 's/uqnoenforce/QUOTA/g' \
		-e 's/gqnoenforce/QUOTA/g' \
		-e 's/pqnoenforce/QUOTA/g' \
		-e 's/qnoenforce/QUOTA/g'  \
		-e "s/QUOTA/$OPTS/g"`

	# ext4 doesn't _do_ "-o pquota/prjquota" because reasons
	# Switch it to "quota" to enable mkfs-time pquota
	if [[ "$FSTYP" == ext[234] ]]; then
		OPTS=`echo $OPTS \
		| sed	-e 's/\bpquota/quota/g'	\
			-e 's/prjquota/quota/g'`
	fi
	# Ensure we have the given quota option - duplicates are fine
	if [ -n "$OPTS" ]; then
		export MOUNT_OPTIONS="$MOUNT_OPTIONS -o $OPTS"
	fi
	echo "MOUNT_OPTIONS = $MOUNT_OPTIONS" >>$seqres.full
}

_check_quota_usage()
{
	# Sync to get delalloc to disk
	sync

	# kill caches to guarantee removal speculative delalloc
	# XXX: really need an ioctl instead of this big hammer
	echo 3 > /proc/sys/vm/drop_caches

	VFS_QUOTA=0
	case $FSTYP in
	ext2|ext3|ext4|ext4dev|f2fs|reiserfs|gfs2|bcachefs)
		VFS_QUOTA=1
		quotaon -f -u -g $SCRATCH_MNT 2>/dev/null
		;;
	xfs)
		# Only way to make this reliable with cow/delalloc/speculative
		# preallocations is to unmount and remount the whole mess...
		_scratch_unmount
		_scratch_mount "-o usrquota,grpquota"
		;;
	*)
		;;
	esac
	repquota -u -n $SCRATCH_MNT  | grep -v "^#0" | _filter_scratch |
		sort >$tmp.user.orig
	repquota -g -n $SCRATCH_MNT  | grep -v "^#0" | _filter_scratch |
		sort >$tmp.group.orig
	if [ $VFS_QUOTA -eq 1 ]; then
		quotacheck -u -g $SCRATCH_MNT 2>/dev/null
	else
		# use XFS method to force quotacheck
		xfs_quota -x -c "off -ug" $SCRATCH_MNT
		_scratch_unmount
		_scratch_mount "-o usrquota,grpquota"
	fi
	repquota -u -n $SCRATCH_MNT  | grep -v "^#0" | _filter_scratch |
		sort >$tmp.user.checked
	repquota -g -n $SCRATCH_MNT  | grep -v "^#0" | _filter_scratch |
		sort >$tmp.group.checked
	if [ $VFS_QUOTA -eq 1 ]; then
		quotaon -u -g $SCRATCH_MNT 2>/dev/null
	fi
	{
		echo "Comparing user usage"
		diff $tmp.user.orig $tmp.user.checked
	} && {
		echo "Comparing group usage"
		diff $tmp.group.orig $tmp.group.checked
	}
}

# Report the block usage of root, $qa_user, and nobody
_report_quota_blocks() {
	repquota $1 | grep -E "^($qa_user|root|nobody)" | awk '{print $1, $3, $4, $5}' | sort -r
}

# Report the inode usage of root, $qa_user, and nobody
_report_quota_inodes() {
	repquota $1 | grep -E "^($qa_user|root|nobody)" | awk '{print $1, $6, $7, $8}' | sort -r
}

# Determine which type of quota we're using
_qsetup()
{
	opt=$1
	enforce=0
	if [ $opt = "u" -o $opt = "uno" ]; then
		type=u
		eval `_choose_uid`
	elif [ $opt = "g" -o $opt = "gno" ]; then
		type=g
		eval `_choose_gid`
	elif [ $opt = "p" -o $opt = "pno" ]; then
		type=p
		eval `_choose_prid`
	fi
	[ $opt = "u" -o $opt = "g" -o $opt = "p" ] && enforce=1

	echo "Using type=$type id=$id" >> $seqres.full
}

# Help to create project quota on directory, works for xfs and other fs.
# Usage: _create_project_quota <dirname> <projid> [name]
# Although the [name] is optional, better to specify it if need a fixed name.
_create_project_quota()
{
	local prjdir=$1
	local id=$2
	local name=$3

	if [ -z "$name" ];then
		name=`echo $projdir | tr \/ \_`
	fi

	rm -rf $prjdir
	mkdir $prjdir
	chmod ugo+rwx $prjdir

	if [ -f /etc/projects -a ! -f $tmp.projects.bk ];then
		cat /etc/projects > $tmp.projects.bk
		echo >/etc/projects
	fi
	if [ -f /etc/projid -a ! -f $tmp.projid.bk ];then
		cat /etc/projid > $tmp.projid.bk
		echo >/etc/projid
	fi

	cat >>/etc/projects <<EOF
$id:$prjdir
EOF
	cat >>/etc/projid <<EOF
$name:$id
EOF
	$XFS_IO_PROG -r -c "chproj $id" -c "chattr +P" $prjdir
}

# If you've called _create_project_quota, then use this function in _cleanup
_restore_project_quota()
{
	if [ -f $tmp.projects.bk ];then
		cat $tmp.projects.bk > /etc/projects && \
			rm -f $tmp.projects.bk
	else
		rm -f /etc/projects
	fi
	if [ -f $tmp.projid.bk ];then
		cat $tmp.projid.bk > /etc/projid && \
			rm -f $tmp.projid.bk
	else
		rm -f /etc/projid
	fi
}

# make sure this script returns success
/bin/true