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