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