xref: /spdk/scripts/check_format.sh (revision a9eea50dc9e5167f529749ae693f51cc0ee3b67b)
1#!/usr/bin/env bash
2#  SPDX-License-Identifier: BSD-3-Clause
3#  Copyright (C) 2015 Intel Corporation
4#  Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES.
5#  All rights reserved.
6#
7if [[ $(uname -s) == Darwin ]]; then
8	# SPDK is not supported on MacOS, but as a developer
9	# convenience we support running the check_format.sh
10	# script on MacOS.
11	# Running "brew install bash coreutils grep" should be
12	# sufficient to get the correct versions of these utilities.
13	if [[ $(type -t mapfile) != builtin ]]; then
14		# We need bash version >= 4.0 for mapfile builtin
15		echo "Please install bash version >= 4.0"
16		exit 1
17	fi
18	if ! hash greadlink 2> /dev/null; then
19		# We need GNU readlink for -f option
20		echo "Please install GNU readlink"
21		exit 1
22	fi
23	if ! hash ggrep 2> /dev/null; then
24		# We need GNU grep for -P option
25		echo "Please install GNU grep"
26		exit 1
27	fi
28	GNU_READLINK="greadlink"
29	GNU_GREP="ggrep"
30else
31	GNU_READLINK="readlink"
32	GNU_GREP="grep"
33fi
34
35rootdir=$($GNU_READLINK -f "$(dirname "$0")")/..
36source "$rootdir/scripts/common.sh"
37
38cd "$rootdir"
39
40# exit on errors
41set -e
42
43if ! hash nproc 2> /dev/null; then
44
45	function nproc() {
46		echo 8
47	}
48
49fi
50
51function version_lt() {
52	[ $(echo -e "$1\n$2" | sort -V | head -1) != "$1" ]
53}
54
55function array_contains_string() {
56	name="$1[@]"
57	array=("${!name}")
58
59	for element in "${array[@]}"; do
60		if [ "$element" = "$2" ]; then
61			return $(true)
62		fi
63	done
64
65	return $(false)
66}
67
68rc=0
69
70function check_permissions() {
71	local rc=0 files=()
72
73	mapfile -t files < <(git ls-files)
74	mapfile -t files < <(get_diffed_dups "${files[@]}")
75
76	((${#files[@]} > 0)) || return 0
77
78	echo -n "Checking file permissions..."
79
80	while read -r perm _res0 _res1 path; do
81		if [ ! -f "$path" ]; then
82			continue
83		fi
84
85		# Skip symlinks
86		if [[ -L $path ]]; then
87			continue
88		fi
89		fname=$(basename -- "$path")
90
91		case ${fname##*.} in
92			c | h | cpp | cc | cxx | hh | hpp | md | html | js | json | svg | Doxyfile | yml | LICENSE | README | conf | in | Makefile | mk | gitignore | go | txt)
93				# These file types should never be executable
94				if [ "$perm" -eq 100755 ]; then
95					echo "ERROR: $path is marked executable but is a code file."
96					rc=1
97				fi
98				;;
99			*)
100				shebang=$(head -n 1 $path | cut -c1-3)
101
102				# git only tracks the execute bit, so will only ever return 755 or 644 as the permission.
103				if [ "$perm" -eq 100755 ]; then
104					# If the file has execute permission, it should start with a shebang.
105					if [ "$shebang" != "#!/" ]; then
106						echo "ERROR: $path is marked executable but does not start with a shebang."
107						rc=1
108					fi
109				else
110					# If the file does not have execute permissions, it should not start with a shebang.
111					if [ "$shebang" = "#!/" ]; then
112						echo "ERROR: $path is not marked executable but starts with a shebang."
113						rc=1
114					fi
115				fi
116				;;
117		esac
118
119	done < <(git ls-files -s "${files[@]}")
120
121	if [ $rc -eq 0 ]; then
122		echo " OK"
123	fi
124
125	return $rc
126}
127
128function check_c_style() {
129	local rc=0
130
131	if hash astyle; then
132		echo -n "Checking coding style..."
133		version=$(astyle --version | awk '{print $NF}')
134		if lt "$version" 3.0.1; then
135			echo " Your astyle version is too old so skipping coding style checks. Please update astyle to at least 3.0.1 version..."
136			rc=1
137		elif ge "$version" 3.4; then
138			echo " Your astyle version is too new so skipping coding style checks. Please use astyle version < 3.4"
139			rc=1
140		else
141			rm -f astyle.log
142			touch astyle.log
143			# Exclude DPDK header files copied into our tree
144			git ls-files '*.[ch]' ':!:*/env_dpdk/*/*.h' ':!:include/linux/fuse_kernel.h' \
145				| xargs -P$(nproc) -n10 astyle --break-return-type --attach-return-type-decl \
146					--options=.astylerc >> astyle.log
147			git ls-files '*.cpp' '*.cc' '*.cxx' '*.hh' '*.hpp' \
148				| xargs -P$(nproc) -n10 astyle --options=.astylerc >> astyle.log
149			if grep -q "^Formatted" astyle.log; then
150				echo " errors detected"
151				git diff --ignore-submodules=all
152				sed -i -e 's/  / /g' astyle.log
153				grep --color=auto "^Formatted.*" astyle.log
154				echo "Incorrect code style detected in one or more files."
155				echo "The files have been automatically formatted."
156				echo "Remember to add the files to your commit."
157				rc=1
158			else
159				echo " OK"
160			fi
161			rm -f astyle.log
162		fi
163	else
164		echo "You do not have astyle installed so your code style is not being checked!"
165	fi
166	return $rc
167}
168
169function check_comment_style() {
170	local rc=0
171
172	echo -n "Checking comment style..."
173
174	git grep --line-number -e '\/[*][^ *-]' -- '*.[ch]' > comment.log || true
175	git grep --line-number -e '[^ ][*]\/' -- '*.[ch]' ':!lib/rte_vhost*/*' >> comment.log || true
176	git grep --line-number -e '^[*]' -- '*.[ch]' ':!include/linux/fuse_kernel.h' >> comment.log || true
177	git grep --line-number -e '\s\/\/' -- '*.[ch]' >> comment.log || true
178	git grep --line-number -e '^\/\/' -- '*.[ch]' >> comment.log || true
179
180	if [ -s comment.log ]; then
181		echo " Incorrect comment formatting detected"
182		cat comment.log
183		rc=1
184	else
185		echo " OK"
186	fi
187	rm -f comment.log
188
189	return $rc
190}
191
192function check_spaces_before_tabs() {
193	local rc=0
194
195	echo -n "Checking for spaces before tabs..."
196	git grep --line-number $' \t' -- './*' ':!*.patch' > whitespace.log || true
197	if [ -s whitespace.log ]; then
198		echo " Spaces before tabs detected"
199		cat whitespace.log
200		rc=1
201	else
202		echo " OK"
203	fi
204	rm -f whitespace.log
205
206	return $rc
207}
208
209function check_trailing_whitespace() {
210	local rc=0
211
212	echo -n "Checking trailing whitespace in output strings..."
213
214	git grep --line-number -e ' \\n"' -- '*.[ch]' > whitespace.log || true
215
216	if [ -s whitespace.log ]; then
217		echo " Incorrect trailing whitespace detected"
218		cat whitespace.log
219		rc=1
220	else
221		echo " OK"
222	fi
223	rm -f whitespace.log
224
225	return $rc
226}
227
228function check_forbidden_functions() {
229	local rc=0
230
231	echo -n "Checking for use of forbidden library functions..."
232
233	git grep --line-number -w '\(atoi\|atol\|atoll\|strncpy\|strcpy\|strcat\|sprintf\|vsprintf\)' -- './*.c' ':!lib/rte_vhost*/**' > badfunc.log || true
234	if [ -s badfunc.log ]; then
235		echo " Forbidden library functions detected"
236		cat badfunc.log
237		rc=1
238	else
239		echo " OK"
240	fi
241	rm -f badfunc.log
242
243	return $rc
244}
245
246function check_cunit_style() {
247	local rc=0
248
249	echo -n "Checking for use of forbidden CUnit macros..."
250
251	git grep --line-number -w 'CU_ASSERT_FATAL' -- 'test/*' ':!include/spdk_internal/cunit.h' > badcunit.log || true
252	if [ -s badcunit.log ]; then
253		echo " Forbidden CU_ASSERT_FATAL usage detected - use SPDK_CU_ASSERT_FATAL instead"
254		cat badcunit.log
255		rc=1
256	else
257		echo " OK"
258	fi
259	rm -f badcunit.log
260
261	return $rc
262}
263
264function check_eof() {
265	local rc=0
266
267	echo -n "Checking blank lines at end of file..."
268
269	if ! git grep -I -l -e . -z './*' ':!*.patch' \
270		| xargs -0 -P$(nproc) -n1 scripts/eofnl > eofnl.log; then
271		echo " Incorrect end-of-file formatting detected"
272		cat eofnl.log
273		rc=1
274	else
275		echo " OK"
276	fi
277	rm -f eofnl.log
278
279	return $rc
280}
281
282function check_posix_includes() {
283	local rc=0
284
285	echo -n "Checking for POSIX includes..."
286	git grep -I -i -f scripts/posix.txt -- './*' ':!include/spdk/stdinc.h' \
287		':!include/linux/**' ':!scripts/posix.txt' ':!lib/env_dpdk/*/*.h' \
288		':!*.patch' ':!configure' > scripts/posix.log || true
289	if [ -s scripts/posix.log ]; then
290		echo "POSIX includes detected. Please include spdk/stdinc.h instead."
291		cat scripts/posix.log
292		rc=1
293	else
294		echo " OK"
295	fi
296	rm -f scripts/posix.log
297
298	return $rc
299}
300
301check_function_conventions() {
302	local rc=0
303
304	echo -n "Checking for proper function naming conventions..."
305	# commit_to_compare = HEAD - 1.
306	commit_to_compare="$(git log --pretty=oneline --skip=1 -n 1 | awk '{print $1}')"
307	failed_naming_conventions=false
308	changed_c_libs=()
309	declared_symbols=()
310
311	# Build an array of all the modified C libraries.
312	mapfile -t changed_c_libs < <(git diff --name-only HEAD $commit_to_compare -- lib/**/*.c module/**/*.c | xargs -r dirname | sort | uniq)
313	# Capture the names of all function declarations
314	mapfile -t declared_symbols < <(grep -Eh 'spdk_[a-zA-Z0-9_]*\(' include/spdk*/*.h \
315		| sed -En 's/.*(spdk[a-z,A-Z,0-9,_]*)\(.*/\1/p')
316
317	for c_lib in "${changed_c_libs[@]}"; do
318		lib_map_file="mk/spdk_blank.map"
319		defined_symbols=()
320		removed_symbols=()
321		exported_symbols=()
322		if ls "$c_lib"/*.map &> /dev/null; then
323			lib_map_file="$(ls "$c_lib"/*.map)"
324		fi
325		# Matching groups are 1. leading +sign. 2, function name 3. argument list / anything after that.
326		# Capture just the names of newly added (or modified) functions that start with "spdk_"
327		mapfile -t defined_symbols < <(git diff -U0 $commit_to_compare HEAD -- $c_lib | sed -En 's/(^[+])(spdk[a-z,A-Z,0-9,_]*)(\(.*)/\2/p')
328		# Capture the names of removed symbols to catch edge cases where we just move definitions around.
329		mapfile -t removed_symbols < <(git diff -U0 $commit_to_compare HEAD -- $c_lib | sed -En 's/(^[-])(spdk[a-z,A-Z,0-9,_]*)(\(.*)/\2/p')
330		for symbol in "${removed_symbols[@]}"; do
331			for i in "${!defined_symbols[@]}"; do
332				if [[ ${defined_symbols[i]} = "$symbol" ]]; then
333					unset -v 'defined_symbols[i]'
334				fi
335			done
336		done
337		# It's possible that we just modified a functions arguments so unfortunately we can't just look at changed lines in this function.
338		# matching groups are 1. All leading whitespace 2. function name. Capture just the symbol name.
339		mapfile -t exported_symbols < <(sed -En 's/(^[[:space:]]*)(spdk[a-z,A-Z,0-9,_]*);/\2/p' < $lib_map_file)
340		for defined_symbol in "${defined_symbols[@]}"; do
341			# if the list of defined symbols is equal to the list of removed symbols, then we are left with a single empty element. skip it.
342			if [ "$defined_symbol" = '' ]; then
343				continue
344			fi
345			not_exported=true
346			not_declared=true
347			if array_contains_string exported_symbols $defined_symbol; then
348				not_exported=false
349			fi
350
351			if array_contains_string declared_symbols $defined_symbol; then
352				not_declared=false
353			fi
354
355			if $not_exported || $not_declared; then
356				if ! $failed_naming_conventions; then
357					echo " found naming convention errors."
358				fi
359				echo "function $defined_symbol starts with spdk_ which is reserved for public API functions."
360				echo "Please add this function to its corresponding map file and a public header or remove the spdk_ prefix."
361				failed_naming_conventions=true
362				rc=1
363			fi
364		done
365	done
366
367	if ! $failed_naming_conventions; then
368		echo " OK"
369	fi
370
371	return $rc
372}
373
374check_conventions_generic() {
375	local out decltype=$1 excludes=(${@:2})
376
377	# We only care about the types defined at the beginning of a line to allow stuff like nested
378	# structs.  Also, we need to drop any __attribute__ declarations.
379	out=$(git grep -E "^$decltype\s+\w+.*\{$" -- "include/spdk" "${excludes[@]}" \
380		| sed 's/__attribute__\s*(([[:alnum:]_, ()]*))\s*//g' \
381		| awk "!/$decltype\s+spdk_/ { \$(NF--)=\"\"; print }")
382
383	if [[ -n "$out" ]]; then
384		cat <<- WARN
385			Found $decltype declarations without the spdk_ prefix:
386
387			$out
388		WARN
389		return 1
390	fi
391}
392
393check_naming_conventions() {
394	check_function_conventions
395	# There are still some enums without the spdk_ prefix.  Once they're renamed, we can remove
396	# these excludes
397	check_conventions_generic 'enum' \
398		':!include/spdk/blob.h' \
399		':!include/spdk/ftl.h' \
400		':!include/spdk/idxd_spec.h' \
401		':!include/spdk/iscsi_spec.h' \
402		':!include/spdk/lvol.h' \
403		':!include/spdk/nvmf_fc_spec.h' \
404		':!include/spdk/vfio_user_spec.h'
405	# Same with structs
406	check_conventions_generic 'struct' \
407		':!include/spdk/ftl.h' \
408		':!include/spdk/idxd_spec.h' \
409		':!include/spdk/iscsi_spec.h' \
410		':!include/spdk/vfio_user_spec.h'
411}
412
413function check_include_style() {
414	local rc=0
415
416	echo -n "Checking #include style..."
417	git grep -I -i --line-number "#include <spdk/" -- '*.[ch]' > scripts/includes.log || true
418	if [ -s scripts/includes.log ]; then
419		echo "Incorrect #include syntax. #includes of spdk/ files should use quotes."
420		cat scripts/includes.log
421		rc=1
422	else
423		echo " OK"
424	fi
425	rm -f scripts/includes.log
426
427	return $rc
428}
429
430function check_opts_structs() {
431	local IFS="|" out types=(
432		spdk_nvme_ns_cmd_ext_io_opts
433		spdk_dif_ctx_init_ext_opts
434	)
435
436	if out=$(git grep -InE "(\.|->)size[[:space:]]*=[[:space:]]*sizeof\(struct (${types[*]})\)"); then
437		cat <<- WARN
438			Found incorrect *ext_opts struct usage.  Use SPDK_SIZEOF() to calculate its size.
439
440			$out
441		WARN
442		return 1
443	fi
444}
445
446function check_attr_packed() {
447	local out
448
449	# For now, we only care about the packed attribute in selected files.  We only check those
450	# used by Timberland (see https://github.com/timberland-sig), as they're using msvc, which
451	# doesn't support the __attribute__ keyword.
452	if out=$(git grep -In '__attribute__((packed))' \
453		'include/spdk/nvme*.h' \
454		'include/spdk/sock.h' \
455		'include/spdk_internal/nvme*.h' \
456		'lib/nvme' 'lib/sock'); then
457		cat <<- WARN
458			Found forbidden __attribute__((packed)).  Try to pack the structures manually or
459			use #pragma pack instead.
460
461			$out
462		WARN
463		return 1
464	fi
465}
466
467function check_python_style() {
468	local rc=0
469
470	if hash pycodestyle 2> /dev/null; then
471		PEP8=pycodestyle
472	elif hash pep8 2> /dev/null; then
473		PEP8=pep8
474	fi
475
476	if [ -n "${PEP8}" ]; then
477		echo -n "Checking Python style..."
478
479		PEP8_ARGS=" --max-line-length=140"
480
481		error=0
482		git ls-files '*.py' | xargs -P$(nproc) -n1 $PEP8 $PEP8_ARGS > pep8.log || error=1
483		if [ $error -ne 0 ]; then
484			echo " Python formatting errors detected"
485			cat pep8.log
486			rc=1
487		else
488			echo " OK"
489		fi
490		rm -f pep8.log
491	else
492		echo "You do not have pycodestyle or pep8 installed so your Python style is not being checked!"
493	fi
494
495	return $rc
496}
497
498function check_golang_style() {
499	local rc=0 out
500
501	if hash golangci-lint 2> /dev/null; then
502		echo -n "Checking Golang style..."
503		gofmtOut=$(gofmt -d . 2>&1)
504		golintOut=$(find "$rootdir/go" -name go.mod -execdir golangci-lint run \; 2>&1)
505		if [[ -n "$gofmtOut" ]] && [[ $gofmtOut == *"diff"* ]]; then
506			cat <<- WARN
507				Golang formatting errors detected:
508				echo "$gofmtOut"
509			WARN
510
511			return 1
512		elif [[ -n "$golintOut" ]]; then
513			cat <<- WARN
514				Golangci lint failed:
515				echo "$golintOut"
516			WARN
517
518			return 1
519		else
520			echo "OK"
521		fi
522	else
523		echo "You do not have golangci-lint installed, so Golang style will not be checked!"
524	fi
525	return 0
526}
527
528function get_bash_files() {
529	local sh shebang
530
531	mapfile -t sh < <(git ls-files '*.sh')
532	mapfile -t shebang < <(git grep -l '^#!.*bash')
533
534	get_diffed_dups "${sh[@]}" "${shebang[@]}"
535}
536
537function check_bash_style() {
538	local rc=0 supported_shfmt_version=v3.8.0
539
540	# find compatible shfmt binary
541	shfmt_bins=($(compgen -c | grep '^shfmt' | uniq || true))
542	for bin in "${shfmt_bins[@]}"; do
543		shfmt_version=$("$bin" --version)
544		if [[ $shfmt_version == "$supported_shfmt_version" ]]; then
545			shfmt=$bin
546			break
547		fi
548	done
549
550	if [ -n "$shfmt" ]; then
551		shfmt_cmdline=() sh_files=()
552
553		mapfile -t sh_files < <(get_bash_files)
554
555		if ((${#sh_files[@]})); then
556			printf 'Checking .sh formatting style...'
557
558			shfmt_cmdline+=(-i 0)     # indent_style = tab|indent_size = 0
559			shfmt_cmdline+=(-bn)      # binary_next_line = true
560			shfmt_cmdline+=(-ci)      # switch_case_indent = true
561			shfmt_cmdline+=(-ln bash) # shell_variant = bash (default)
562			shfmt_cmdline+=(-d)       # diffOut - print diff of the changes and exit with != 0
563			shfmt_cmdline+=(-sr)      # redirect operators will be followed by a space
564
565			diff=${output_dir:-$PWD}/$shfmt.patch
566
567			if ! "$shfmt" "${shfmt_cmdline[@]}" "${sh_files[@]}" > "$diff"; then
568				# In case shfmt detects an actual syntax error it will write out a proper message on
569				# its stderr, hence the diff file should remain empty.
570				rc=1
571				if [[ -s $diff ]]; then
572					if patch --merge -p0 < "$diff"; then
573						diff_out=$(git diff)
574
575						if [[ -n $diff_out ]]; then
576							cat <<- ERROR_SHFMT
577
578								* Errors in style formatting have been detected.
579								  Please, review the generated patch at $diff
580
581								# _START_OF_THE_DIFF
582
583								$diff_out
584
585								# _END_OF_THE_DIFF
586
587							ERROR_SHFMT
588						else
589							# Empty diff? This likely means that we reverted to a clean state
590							printf '* Patch reverted, please review your changes and %s\n' "$diff"
591						fi
592					else
593						printf '* Failed to apply %s\n' "$diff"
594
595					fi
596				fi
597			else
598				rm -f "$diff"
599				printf ' OK\n'
600			fi
601		fi
602	else
603		cat <<- MSG
604			Supported version of shfmt not detected${shfmt_bins[*]:+ (only ${shfmt_bins[*]} is available)}.  Bash style
605			formatting check is skipped.  shfmt-$supported_shfmt_version can be installed using 'scripts/pkgdep.sh -d'.
606		MSG
607	fi
608
609	# Cleanup potential .orig files that shfmt creates
610	local orig_f
611
612	mapfile -t orig_f < <(git diff --name-only)
613	orig_f=("${orig_f[@]/%/.orig}")
614
615	if ((${#orig_f[@]} > 0)); then
616		git clean -f "${orig_f[@]}"
617	fi
618
619	return $rc
620}
621
622function check_bash_static_analysis() {
623	local rc=0 files=()
624
625	mapfile -t files < <(get_bash_files)
626	((${#files[@]} > 0)) || return 0
627
628	if hash shellcheck 2> /dev/null; then
629		echo -n "Checking Bash static analysis with shellcheck..."
630
631		shellcheck_v=$(shellcheck --version | grep -P "version: [0-9\.]+" | cut -d " " -f2)
632
633		# SHCK_EXCLUDE contains a list of all of the spellcheck errors found in SPDK scripts
634		# currently. New errors should only be added to this list if the cost of fixing them
635		# is deemed too high. For more information about the errors, go to:
636		# https://github.com/koalaman/shellcheck/wiki/Checks
637		# Error descriptions can also be found at: https://github.com/koalaman/shellcheck/wiki
638		# SPDK fails some error checks which have been deprecated in later versions of shellcheck.
639		# We will not try to fix these error checks, but instead just leave the error types here
640		# so that we can still run with older versions of shellcheck.
641		SHCK_EXCLUDE="SC1117"
642		# SPDK has decided to not fix violations of these errors.
643		# We are aware about below exclude list and we want this errors to be excluded.
644		# SC1083: This {/} is literal. Check expression (missing ;/\n?) or quote it.
645		# SC1090: Can't follow non-constant source. Use a directive to specify location.
646		# SC1091: Not following: (error message here)
647		# SC2001: See if you can use ${variable//search/replace} instead.
648		# SC2010: Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames.
649		# SC2015: Note that A && B || C is not if-then-else. C may run when A is true.
650		# SC2016: Expressions don't expand in single quotes, use double quotes for that.
651		# SC2034: foo appears unused. Verify it or export it.
652		# SC2046: Quote this to prevent word splitting.
653		# SC2086: Double quote to prevent globbing and word splitting.
654		# SC2119: Use foo "$@" if function's $1 should mean script's $1.
655		# SC2120: foo references arguments, but none are ever passed.
656		# SC2128: Expanding an array without an index only gives the first element.
657		# SC2148: Add shebang to the top of your script.
658		# SC2153: Possible Misspelling: MYVARIABLE may not be assigned, but MY_VARIABLE is.
659		# SC2154: var is referenced but not assigned.
660		# SC2164: Use cd ... || exit in case cd fails.
661		# SC2174: When used with -p, -m only applies to the deepest directory.
662		# SC2178: Variable was used as an array but is now assigned a string.
663		# SC2206: Quote to prevent word splitting/globbing,
664		#         or split robustly with mapfile or read -a.
665		# SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting).
666		# SC2223: This default assignment may cause DoS due to globbing. Quote it.
667		SHCK_EXCLUDE="$SHCK_EXCLUDE,SC1083,SC1090,SC1091,SC2010,SC2015,SC2016,SC2034,SC2046,SC2086,\
668SC2119,SC2120,SC2128,SC2148,SC2153,SC2154,SC2164,SC2174,SC2178,SC2001,SC2206,SC2207,SC2223"
669
670		SHCK_FORMAT="tty"
671		SHCK_APPLY=false
672		SHCH_ARGS="-e $SHCK_EXCLUDE -f $SHCK_FORMAT"
673
674		if ge "$shellcheck_v" 0.4.0; then
675			SHCH_ARGS+=" -x"
676		else
677			echo "shellcheck $shellcheck_v detected, recommended >= 0.4.0."
678		fi
679
680		printf '%s\n' "${files[@]}" | xargs -P$(nproc) -n1 shellcheck $SHCH_ARGS &> shellcheck.log
681		if [[ -s shellcheck.log ]]; then
682			echo " Bash shellcheck errors detected!"
683
684			cat shellcheck.log
685			if $SHCK_APPLY; then
686				git apply shellcheck.log
687				echo "Bash errors were automatically corrected."
688				echo "Please remember to add the changes to your commit."
689			fi
690			rc=1
691		else
692			echo " OK"
693		fi
694		rm -f shellcheck.log
695	else
696		echo "You do not have shellcheck installed so your Bash static analysis is not being performed!"
697	fi
698
699	return $rc
700}
701
702function check_changelog() {
703	local rc=0
704
705	# Check if any of the public interfaces were modified by this patch.
706	# Warn the user to consider updating the changelog any changes
707	# are detected.
708	echo -n "Checking whether CHANGELOG.md should be updated..."
709	staged=$(git diff --name-only --cached .)
710	working=$(git status -s --porcelain --ignore-submodules | grep -iv "??" | awk '{print $2}')
711	files="$staged $working"
712	if [[ "$files" = " " ]]; then
713		files=$(git diff-tree --no-commit-id --name-only -r HEAD)
714	fi
715
716	has_changelog=0
717	for f in $files; do
718		if [[ $f == CHANGELOG.md ]]; then
719			# The user has a changelog entry, so exit.
720			has_changelog=1
721			break
722		fi
723	done
724
725	needs_changelog=0
726	if [ $has_changelog -eq 0 ]; then
727		for f in $files; do
728			if [[ $f == include/spdk/* ]] || [[ $f == scripts/rpc.py ]] || [[ $f == etc/* ]]; then
729				echo ""
730				echo -n "$f was modified. Consider updating CHANGELOG.md."
731				needs_changelog=1
732			fi
733		done
734	fi
735
736	if [ $needs_changelog -eq 0 ]; then
737		echo " OK"
738	else
739		echo ""
740	fi
741
742	return $rc
743}
744
745function check_json_rpc() {
746	local rc=0
747
748	echo -n "Checking that all RPCs are documented..."
749	while IFS='"' read -r _ rpc _; do
750		if ! grep -q "^### $rpc" doc/jsonrpc.md; then
751			echo "Missing JSON-RPC documentation for ${rpc}"
752			rc=1
753		fi
754	done < <(git grep -h -E "^SPDK_RPC_REGISTER\(" ':!test/*' ':!examples/*')
755
756	if [ $rc -eq 0 ]; then
757		echo " OK"
758	fi
759	return $rc
760}
761
762function check_markdown_format() {
763	local rc=0
764
765	if hash mdl 2> /dev/null; then
766		echo -n "Checking markdown files format..."
767		mdl -g -s $rootdir/mdl_rules.rb . > mdl.log || true
768		if [ -s mdl.log ]; then
769			echo " Errors in .md files detected:"
770			cat mdl.log
771			rc=1
772		else
773			echo " OK"
774		fi
775		rm -f mdl.log
776	else
777		echo "You do not have markdownlint installed so .md files not being checked!"
778	fi
779
780	return $rc
781}
782
783function check_rpc_args() {
784	local rc=0
785
786	echo -n "Checking rpc.py argument option names..."
787	grep add_argument scripts/rpc.py | $GNU_GREP -oP "(?<=--)[a-z0-9\-\_]*(?=\')" | grep "_" > badargs.log
788
789	if [[ -s badargs.log ]]; then
790		echo "rpc.py arguments with underscores detected!"
791		cat badargs.log
792		echo "Please convert the underscores to dashes."
793		rc=1
794	else
795		echo " OK"
796	fi
797	rm -f badargs.log
798	return $rc
799}
800
801function get_files_for_lic() {
802	local f_shebang="" f_suffix=() f_type=() f_all=() exceptions=""
803
804	f_shebang+="bash|"
805	f_shebang+="make|"
806	f_shebang+="perl|"
807	f_shebang+="python|"
808	f_shebang+="sh"
809
810	f_suffix+=("*.c")
811	f_suffix+=("*.cpp")
812	f_suffix+=("*.h")
813	f_suffix+=("*.go")
814	f_suffix+=("*.mk")
815	f_suffix+=("*.pl")
816	f_suffix+=("*.py")
817	f_suffix+=("*.sh")
818	f_suffix+=("*.yaml")
819
820	f_type+=("*Dockerfile")
821	f_type+=("*Makefile")
822
823	# Exclude files that may match the above types but should not
824	# fall under SPDX check.
825	exceptions+="include/linux|"
826	exceptions+="include/spdk/queue_extras.h|"
827	exceptions+="lib/mlx5/mlx5_ifc.h"
828
829	mapfile -t f_all < <(
830		git ls-files "${f_suffix[@]}" "${f_type[@]}"
831		git grep -lE "^#!.*($f_shebang)"
832	)
833
834	printf '%s\n' "${f_all[@]}" | sort -u | grep -vE "$exceptions"
835}
836
837function check_spdx_lic() {
838	local files_missing_license_header=() hint=()
839	local rc=0
840
841	hint+=("SPDX-License-Identifier: BSD-3-Clause")
842	hint+=("All rights reserved.")
843
844	printf 'Checking SPDX-license...'
845
846	mapfile -t files_missing_license_header < <(
847		grep -LE "SPDX-License-Identifier:.+" $(get_files_for_lic)
848	)
849
850	if ((${#files_missing_license_header[@]} > 0)); then
851		printf '\nFollowing files are missing SPDX-license header:\n'
852		printf '  @%s\n' "${files_missing_license_header[@]}"
853		printf '\nExample:\n'
854		printf '  #  %s\n' "${hint[@]}"
855		return 1
856	fi
857
858	printf 'OK\n'
859}
860
861function get_diffed_files() {
862	# Get files where changes are meant to be committed
863	git diff --name-only HEAD HEAD~1
864	# Get files from staging
865	git diff --name-only --cached HEAD
866	git diff --name-only HEAD
867}
868
869function get_diffed_dups() {
870	local files=("$@") diff=() _diff=()
871
872	# Sort and get rid of duplicates from the main list
873	mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u)
874	# Get staged|committed files
875	mapfile -t diff < <(get_diffed_files | sort -u)
876
877	if [[ ! -v CHECK_FORMAT_ONLY_DIFF ]]; then
878		# Just return the main list
879		printf '%s\n' "${files[@]}"
880		return 0
881	fi
882
883	if ((${#diff[@]} > 0)); then
884		# Check diff'ed files against the main list to see if they are a subset
885		# of it. If yes, then we return the duplicates which are the files that
886		# should be committed, modified.
887		mapfile -t _diff < <(
888			printf '%s\n' "${diff[@]}" "${files[@]}" | sort | uniq -d
889		)
890		if ((${#_diff[@]} > 0)); then
891			printf '%s\n' "${_diff[@]}"
892			return 0
893		fi
894	fi
895}
896
897function check_extern_c() {
898	local files_missing_extern_c=()
899
900	printf 'Checking extern "C"... '
901
902	mapfile -t files_missing_extern_c < <(
903		grep -LE "extern \"C\"" $(git ls-files -- ./include/spdk/*.h)
904	)
905
906	if ((${#files_missing_extern_c[@]} > 0)); then
907		printf '\nFollowing header files are missing extern C:\n'
908		printf '  %s\n' "${files_missing_extern_c[@]}"
909		return 1
910	fi
911
912	printf 'OK\n'
913}
914
915rc=0
916
917check_permissions || rc=1
918check_c_style || rc=1
919
920GIT_VERSION=$(git --version | cut -d' ' -f3)
921
922if version_lt "1.9.5" "${GIT_VERSION}"; then
923	# git <1.9.5 doesn't support pathspec magic exclude
924	echo " Your git version is too old to perform all tests. Please update git to at least 1.9.5 version..."
925	exit $rc
926fi
927
928check_comment_style || rc=1
929check_markdown_format || rc=1
930check_spaces_before_tabs || rc=1
931check_trailing_whitespace || rc=1
932check_forbidden_functions || rc=1
933check_cunit_style || rc=1
934check_eof || rc=1
935check_posix_includes || rc=1
936check_naming_conventions || rc=1
937check_include_style || rc=1
938check_opts_structs || rc=1
939check_attr_packed || rc=1
940check_python_style || rc=1
941check_bash_style || rc=1
942check_bash_static_analysis || rc=1
943check_changelog || rc=1
944check_json_rpc || rc=1
945check_rpc_args || rc=1
946check_spdx_lic || rc=1
947check_golang_style || rc=1
948check_extern_c || rc=1
949
950exit $rc
951