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