xref: /spdk/scripts/check_format.sh (revision 7ff7ec0ed88d5acad07010b6c577326debc22f7c)
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 supported_shfmt_version=v3.8.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 != "$supported_shfmt_version" ]]; then
544			echo "$bin version $shfmt_version not used (only $supported_shfmt_version is supported)"
545			echo "$supported_shfmt_version 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			if ! "$shfmt" "${shfmt_cmdline[@]}" "${sh_files[@]}" > "$diff"; then
570				# In case shfmt detects an actual syntax error it will write out a proper message on
571				# its stderr, hence the diff file should remain empty.
572				rc=1
573				if [[ -s $diff ]]; then
574					if patch --merge -p0 < "$diff"; then
575						diff_out=$(git diff)
576
577						if [[ -n $diff_out ]]; then
578							cat <<- ERROR_SHFMT
579
580								* Errors in style formatting have been detected.
581								  Please, review the generated patch at $diff
582
583								# _START_OF_THE_DIFF
584
585								$diff_out
586
587								# _END_OF_THE_DIFF
588
589							ERROR_SHFMT
590						else
591							# Empty diff? This likely means that we reverted to a clean state
592							printf '* Patch reverted, please review your changes and %s\n' "$diff"
593						fi
594					else
595						printf '* Failed to apply %s\n' "$diff"
596
597					fi
598				fi
599			else
600				rm -f "$diff"
601				printf ' OK\n'
602			fi
603		fi
604	else
605		echo "Supported version of shfmt not detected, Bash style formatting check is skipped"
606	fi
607
608	# Cleanup potential .orig files that shfmt creates
609	local orig_f
610
611	mapfile -t orig_f < <(git diff --name-only)
612	orig_f=("${orig_f[@]/%/.orig}")
613
614	if ((${#orig_f[@]} > 0)); then
615		git clean -f "${orig_f[@]}"
616	fi
617
618	return $rc
619}
620
621function check_bash_static_analysis() {
622	local rc=0 files=()
623
624	mapfile -t files < <(get_bash_files)
625	((${#files[@]} > 0)) || return 0
626
627	if hash shellcheck 2> /dev/null; then
628		echo -n "Checking Bash static analysis with shellcheck..."
629
630		shellcheck_v=$(shellcheck --version | grep -P "version: [0-9\.]+" | cut -d " " -f2)
631
632		# SHCK_EXCLUDE contains a list of all of the spellcheck errors found in SPDK scripts
633		# currently. New errors should only be added to this list if the cost of fixing them
634		# is deemed too high. For more information about the errors, go to:
635		# https://github.com/koalaman/shellcheck/wiki/Checks
636		# Error descriptions can also be found at: https://github.com/koalaman/shellcheck/wiki
637		# SPDK fails some error checks which have been deprecated in later versions of shellcheck.
638		# We will not try to fix these error checks, but instead just leave the error types here
639		# so that we can still run with older versions of shellcheck.
640		SHCK_EXCLUDE="SC1117"
641		# SPDK has decided to not fix violations of these errors.
642		# We are aware about below exclude list and we want this errors to be excluded.
643		# SC1083: This {/} is literal. Check expression (missing ;/\n?) or quote it.
644		# SC1090: Can't follow non-constant source. Use a directive to specify location.
645		# SC1091: Not following: (error message here)
646		# SC2001: See if you can use ${variable//search/replace} instead.
647		# SC2010: Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames.
648		# SC2015: Note that A && B || C is not if-then-else. C may run when A is true.
649		# SC2016: Expressions don't expand in single quotes, use double quotes for that.
650		# SC2034: foo appears unused. Verify it or export it.
651		# SC2046: Quote this to prevent word splitting.
652		# SC2086: Double quote to prevent globbing and word splitting.
653		# SC2119: Use foo "$@" if function's $1 should mean script's $1.
654		# SC2120: foo references arguments, but none are ever passed.
655		# SC2128: Expanding an array without an index only gives the first element.
656		# SC2148: Add shebang to the top of your script.
657		# SC2153: Possible Misspelling: MYVARIABLE may not be assigned, but MY_VARIABLE is.
658		# SC2154: var is referenced but not assigned.
659		# SC2164: Use cd ... || exit in case cd fails.
660		# SC2174: When used with -p, -m only applies to the deepest directory.
661		# SC2178: Variable was used as an array but is now assigned a string.
662		# SC2206: Quote to prevent word splitting/globbing,
663		#         or split robustly with mapfile or read -a.
664		# SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting).
665		# SC2223: This default assignment may cause DoS due to globbing. Quote it.
666		SHCK_EXCLUDE="$SHCK_EXCLUDE,SC1083,SC1090,SC1091,SC2010,SC2015,SC2016,SC2034,SC2046,SC2086,\
667SC2119,SC2120,SC2128,SC2148,SC2153,SC2154,SC2164,SC2174,SC2178,SC2001,SC2206,SC2207,SC2223"
668
669		SHCK_FORMAT="tty"
670		SHCK_APPLY=false
671		SHCH_ARGS="-e $SHCK_EXCLUDE -f $SHCK_FORMAT"
672
673		if ge "$shellcheck_v" 0.4.0; then
674			SHCH_ARGS+=" -x"
675		else
676			echo "shellcheck $shellcheck_v detected, recommended >= 0.4.0."
677		fi
678
679		printf '%s\n' "${files[@]}" | xargs -P$(nproc) -n1 shellcheck $SHCH_ARGS &> shellcheck.log
680		if [[ -s shellcheck.log ]]; then
681			echo " Bash shellcheck errors detected!"
682
683			cat shellcheck.log
684			if $SHCK_APPLY; then
685				git apply shellcheck.log
686				echo "Bash errors were automatically corrected."
687				echo "Please remember to add the changes to your commit."
688			fi
689			rc=1
690		else
691			echo " OK"
692		fi
693		rm -f shellcheck.log
694	else
695		echo "You do not have shellcheck installed so your Bash static analysis is not being performed!"
696	fi
697
698	return $rc
699}
700
701function check_changelog() {
702	local rc=0
703
704	# Check if any of the public interfaces were modified by this patch.
705	# Warn the user to consider updating the changelog any changes
706	# are detected.
707	echo -n "Checking whether CHANGELOG.md should be updated..."
708	staged=$(git diff --name-only --cached .)
709	working=$(git status -s --porcelain --ignore-submodules | grep -iv "??" | awk '{print $2}')
710	files="$staged $working"
711	if [[ "$files" = " " ]]; then
712		files=$(git diff-tree --no-commit-id --name-only -r HEAD)
713	fi
714
715	has_changelog=0
716	for f in $files; do
717		if [[ $f == CHANGELOG.md ]]; then
718			# The user has a changelog entry, so exit.
719			has_changelog=1
720			break
721		fi
722	done
723
724	needs_changelog=0
725	if [ $has_changelog -eq 0 ]; then
726		for f in $files; do
727			if [[ $f == include/spdk/* ]] || [[ $f == scripts/rpc.py ]] || [[ $f == etc/* ]]; then
728				echo ""
729				echo -n "$f was modified. Consider updating CHANGELOG.md."
730				needs_changelog=1
731			fi
732		done
733	fi
734
735	if [ $needs_changelog -eq 0 ]; then
736		echo " OK"
737	else
738		echo ""
739	fi
740
741	return $rc
742}
743
744function check_json_rpc() {
745	local rc=0
746
747	echo -n "Checking that all RPCs are documented..."
748	while IFS='"' read -r _ rpc _; do
749		if ! grep -q "^### $rpc" doc/jsonrpc.md; then
750			echo "Missing JSON-RPC documentation for ${rpc}"
751			rc=1
752		fi
753	done < <(git grep -h -E "^SPDK_RPC_REGISTER\(" ':!test/*' ':!examples/*')
754
755	if [ $rc -eq 0 ]; then
756		echo " OK"
757	fi
758	return $rc
759}
760
761function check_markdown_format() {
762	local rc=0
763
764	if hash mdl 2> /dev/null; then
765		echo -n "Checking markdown files format..."
766		mdl -g -s $rootdir/mdl_rules.rb . > mdl.log || true
767		if [ -s mdl.log ]; then
768			echo " Errors in .md files detected:"
769			cat mdl.log
770			rc=1
771		else
772			echo " OK"
773		fi
774		rm -f mdl.log
775	else
776		echo "You do not have markdownlint installed so .md files not being checked!"
777	fi
778
779	return $rc
780}
781
782function check_rpc_args() {
783	local rc=0
784
785	echo -n "Checking rpc.py argument option names..."
786	grep add_argument scripts/rpc.py | $GNU_GREP -oP "(?<=--)[a-z0-9\-\_]*(?=\')" | grep "_" > badargs.log
787
788	if [[ -s badargs.log ]]; then
789		echo "rpc.py arguments with underscores detected!"
790		cat badargs.log
791		echo "Please convert the underscores to dashes."
792		rc=1
793	else
794		echo " OK"
795	fi
796	rm -f badargs.log
797	return $rc
798}
799
800function get_files_for_lic() {
801	local f_shebang="" f_suffix=() f_type=() f_all=() exceptions=""
802
803	f_shebang+="bash|"
804	f_shebang+="make|"
805	f_shebang+="perl|"
806	f_shebang+="python|"
807	f_shebang+="sh"
808
809	f_suffix+=("*.c")
810	f_suffix+=("*.cpp")
811	f_suffix+=("*.h")
812	f_suffix+=("*.go")
813	f_suffix+=("*.mk")
814	f_suffix+=("*.pl")
815	f_suffix+=("*.py")
816	f_suffix+=("*.sh")
817	f_suffix+=("*.yaml")
818
819	f_type+=("*Dockerfile")
820	f_type+=("*Makefile")
821
822	# Exclude files that may match the above types but should not
823	# fall under SPDX check.
824	exceptions+="include/linux|"
825	exceptions+="include/spdk/queue_extras.h"
826
827	mapfile -t f_all < <(
828		git ls-files "${f_suffix[@]}" "${f_type[@]}"
829		git grep -lE "^#!.*($f_shebang)"
830	)
831
832	printf '%s\n' "${f_all[@]}" | sort -u | grep -vE "$exceptions"
833}
834
835function check_spdx_lic() {
836	local files_missing_license_header=() hint=()
837	local rc=0
838
839	hint+=("SPDX-License-Identifier: BSD-3-Clause")
840	hint+=("All rights reserved.")
841
842	printf 'Checking SPDX-license...'
843
844	mapfile -t files_missing_license_header < <(
845		grep -LE "SPDX-License-Identifier:.+" $(get_files_for_lic)
846	)
847
848	if ((${#files_missing_license_header[@]} > 0)); then
849		printf '\nFollowing files are missing SPDX-license header:\n'
850		printf '  @%s\n' "${files_missing_license_header[@]}"
851		printf '\nExample:\n'
852		printf '  #  %s\n' "${hint[@]}"
853		return 1
854	fi
855
856	printf 'OK\n'
857}
858
859function get_diffed_files() {
860	# Get files where changes are meant to be committed
861	git diff --name-only HEAD HEAD~1
862	# Get files from staging
863	git diff --name-only --cached HEAD
864	git diff --name-only HEAD
865}
866
867function get_diffed_dups() {
868	local files=("$@") diff=() _diff=()
869
870	# Sort and get rid of duplicates from the main list
871	mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u)
872	# Get staged|committed files
873	mapfile -t diff < <(get_diffed_files | sort -u)
874
875	if [[ ! -v CHECK_FORMAT_ONLY_DIFF ]]; then
876		# Just return the main list
877		printf '%s\n' "${files[@]}"
878		return 0
879	fi
880
881	if ((${#diff[@]} > 0)); then
882		# Check diff'ed files against the main list to see if they are a subset
883		# of it. If yes, then we return the duplicates which are the files that
884		# should be committed, modified.
885		mapfile -t _diff < <(
886			printf '%s\n' "${diff[@]}" "${files[@]}" | sort | uniq -d
887		)
888		if ((${#_diff[@]} > 0)); then
889			printf '%s\n' "${_diff[@]}"
890			return 0
891		fi
892	fi
893}
894
895function check_extern_c() {
896	local files_missing_extern_c=()
897
898	printf 'Checking extern "C"... '
899
900	mapfile -t files_missing_extern_c < <(
901		grep -LE "extern \"C\"" $(git ls-files -- ./include/spdk/*.h)
902	)
903
904	if ((${#files_missing_extern_c[@]} > 0)); then
905		printf '\nFollowing header files are missing extern C:\n'
906		printf '  %s\n' "${files_missing_extern_c[@]}"
907		return 1
908	fi
909
910	printf 'OK\n'
911}
912
913rc=0
914
915check_permissions || rc=1
916check_c_style || rc=1
917
918GIT_VERSION=$(git --version | cut -d' ' -f3)
919
920if version_lt "1.9.5" "${GIT_VERSION}"; then
921	# git <1.9.5 doesn't support pathspec magic exclude
922	echo " Your git version is too old to perform all tests. Please update git to at least 1.9.5 version..."
923	exit $rc
924fi
925
926check_comment_style || rc=1
927check_markdown_format || rc=1
928check_spaces_before_tabs || rc=1
929check_trailing_whitespace || rc=1
930check_forbidden_functions || rc=1
931check_cunit_style || rc=1
932check_eof || rc=1
933check_posix_includes || rc=1
934check_naming_conventions || rc=1
935check_include_style || rc=1
936check_opts_structs || rc=1
937check_attr_packed || rc=1
938check_python_style || rc=1
939check_bash_style || rc=1
940check_bash_static_analysis || rc=1
941check_changelog || rc=1
942check_json_rpc || rc=1
943check_rpc_args || rc=1
944check_spdx_lic || rc=1
945check_golang_style || rc=1
946check_extern_c || rc=1
947
948exit $rc
949