xref: /spdk/scripts/check_format.sh (revision 60982c759db49b4f4579f16e3b24df0725ba4b94)
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
295function check_naming_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
368function check_include_style() {
369	local rc=0
370
371	echo -n "Checking #include style..."
372	git grep -I -i --line-number "#include <spdk/" -- '*.[ch]' > scripts/includes.log || true
373	if [ -s scripts/includes.log ]; then
374		echo "Incorrect #include syntax. #includes of spdk/ files should use quotes."
375		cat scripts/includes.log
376		rc=1
377	else
378		echo " OK"
379	fi
380	rm -f scripts/includes.log
381
382	return $rc
383}
384
385function check_opts_structs() {
386	local IFS="|" out types=(
387		spdk_nvme_ns_cmd_ext_io_opts
388		spdk_dif_ctx_init_ext_opts
389	)
390
391	if out=$(git grep -InE "(\.|->)size[[:space:]]*=[[:space:]]*sizeof\(struct (${types[*]})\)"); then
392		cat <<- WARN
393			Found incorrect *ext_opts struct usage.  Use SPDK_SIZEOF() to calculate its size.
394
395			$out
396		WARN
397		return 1
398	fi
399}
400
401function check_attr_packed() {
402	local out
403
404	# For now, we only care about the packed attribute in selected files.  We only check those
405	# used by Timberland (see https://github.com/timberland-sig), as they're using msvc, which
406	# doesn't support the __attribute__ keyword.
407	if out=$(git grep -In '__attribute__((packed))' \
408		'include/spdk/nvme*.h' \
409		'include/spdk/sock.h' \
410		'include/spdk_internal/nvme*.h' \
411		'lib/nvme' 'lib/sock'); then
412		cat <<- WARN
413			Found forbidden __attribute__((packed)).  Try to pack the structures manually or
414			use #pragma pack instead.
415
416			$out
417		WARN
418		return 1
419	fi
420}
421
422function check_python_style() {
423	local rc=0
424
425	if hash pycodestyle 2> /dev/null; then
426		PEP8=pycodestyle
427	elif hash pep8 2> /dev/null; then
428		PEP8=pep8
429	fi
430
431	if [ -n "${PEP8}" ]; then
432		echo -n "Checking Python style..."
433
434		PEP8_ARGS=" --max-line-length=140"
435
436		error=0
437		git ls-files '*.py' | xargs -P$(nproc) -n1 $PEP8 $PEP8_ARGS > pep8.log || error=1
438		if [ $error -ne 0 ]; then
439			echo " Python formatting errors detected"
440			cat pep8.log
441			rc=1
442		else
443			echo " OK"
444		fi
445		rm -f pep8.log
446	else
447		echo "You do not have pycodestyle or pep8 installed so your Python style is not being checked!"
448	fi
449
450	return $rc
451}
452
453function get_bash_files() {
454	local sh shebang
455
456	mapfile -t sh < <(git ls-files '*.sh')
457	mapfile -t shebang < <(git grep -l '^#!.*bash')
458
459	get_diffed_dups "${sh[@]}" "${shebang[@]}"
460}
461
462function check_bash_style() {
463	local rc=0
464
465	# find compatible shfmt binary
466	shfmt_bins=$(compgen -c | grep '^shfmt' | uniq || true)
467	for bin in $shfmt_bins; do
468		shfmt_version=$("$bin" --version)
469		if [ $shfmt_version != "v3.1.0" ]; then
470			echo "$bin version $shfmt_version not used (only v3.1.0 is supported)"
471			echo "v3.1.0 can be installed using 'scripts/pkgdep.sh -d'"
472		else
473			shfmt=$bin
474			break
475		fi
476	done
477
478	if [ -n "$shfmt" ]; then
479		shfmt_cmdline=() sh_files=()
480
481		mapfile -t sh_files < <(get_bash_files)
482
483		if ((${#sh_files[@]})); then
484			printf 'Checking .sh formatting style...'
485
486			shfmt_cmdline+=(-i 0)     # indent_style = tab|indent_size = 0
487			shfmt_cmdline+=(-bn)      # binary_next_line = true
488			shfmt_cmdline+=(-ci)      # switch_case_indent = true
489			shfmt_cmdline+=(-ln bash) # shell_variant = bash (default)
490			shfmt_cmdline+=(-d)       # diffOut - print diff of the changes and exit with != 0
491			shfmt_cmdline+=(-sr)      # redirect operators will be followed by a space
492
493			diff=${output_dir:-$PWD}/$shfmt.patch
494
495			# Explicitly tell shfmt to not look for .editorconfig. .editorconfig is also not looked up
496			# in case any formatting arguments has been passed on its cmdline.
497			if ! SHFMT_NO_EDITORCONFIG=true "$shfmt" "${shfmt_cmdline[@]}" "${sh_files[@]}" > "$diff"; then
498				# In case shfmt detects an actual syntax error it will write out a proper message on
499				# its stderr, hence the diff file should remain empty.
500				rc=1
501				if [[ -s $diff ]]; then
502					if patch --merge -p0 < "$diff"; then
503						diff_out=$(git diff)
504
505						if [[ -n $diff_out ]]; then
506							cat <<- ERROR_SHFMT
507
508								* Errors in style formatting have been detected.
509								  Please, review the generated patch at $diff
510
511								# _START_OF_THE_DIFF
512
513								$diff_out
514
515								# _END_OF_THE_DIFF
516
517							ERROR_SHFMT
518						else
519							# Empty diff? This likely means that we reverted to a clean state
520							printf '* Patch reverted, please review your changes and %s\n' "$diff"
521						fi
522					else
523						printf '* Failed to apply %s\n' "$diff"
524
525					fi
526				fi
527			else
528				rm -f "$diff"
529				printf ' OK\n'
530			fi
531		fi
532	else
533		echo "Supported version of shfmt not detected, Bash style formatting check is skipped"
534	fi
535
536	# Cleanup potential .orig files that shfmt creates
537	local orig_f
538
539	mapfile -t orig_f < <(git diff --name-only)
540	orig_f=("${orig_f[@]/%/.orig}")
541
542	if ((${#orig_f[@]} > 0)); then
543		git clean -f "${orig_f[@]}"
544	fi
545
546	return $rc
547}
548
549function check_bash_static_analysis() {
550	local rc=0 files=()
551
552	mapfile -t files < <(get_bash_files)
553	((${#files[@]} > 0)) || return 0
554
555	if hash shellcheck 2> /dev/null; then
556		echo -n "Checking Bash static analysis with shellcheck..."
557
558		shellcheck_v=$(shellcheck --version | grep -P "version: [0-9\.]+" | cut -d " " -f2)
559
560		# SHCK_EXCLUDE contains a list of all of the spellcheck errors found in SPDK scripts
561		# currently. New errors should only be added to this list if the cost of fixing them
562		# is deemed too high. For more information about the errors, go to:
563		# https://github.com/koalaman/shellcheck/wiki/Checks
564		# Error descriptions can also be found at: https://github.com/koalaman/shellcheck/wiki
565		# SPDK fails some error checks which have been deprecated in later versions of shellcheck.
566		# We will not try to fix these error checks, but instead just leave the error types here
567		# so that we can still run with older versions of shellcheck.
568		SHCK_EXCLUDE="SC1117"
569		# SPDK has decided to not fix violations of these errors.
570		# We are aware about below exclude list and we want this errors to be excluded.
571		# SC1083: This {/} is literal. Check expression (missing ;/\n?) or quote it.
572		# SC1090: Can't follow non-constant source. Use a directive to specify location.
573		# SC1091: Not following: (error message here)
574		# SC2001: See if you can use ${variable//search/replace} instead.
575		# SC2010: Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames.
576		# SC2015: Note that A && B || C is not if-then-else. C may run when A is true.
577		# SC2016: Expressions don't expand in single quotes, use double quotes for that.
578		# SC2034: foo appears unused. Verify it or export it.
579		# SC2046: Quote this to prevent word splitting.
580		# SC2086: Double quote to prevent globbing and word splitting.
581		# SC2119: Use foo "$@" if function's $1 should mean script's $1.
582		# SC2120: foo references arguments, but none are ever passed.
583		# SC2128: Expanding an array without an index only gives the first element.
584		# SC2148: Add shebang to the top of your script.
585		# SC2153: Possible Misspelling: MYVARIABLE may not be assigned, but MY_VARIABLE is.
586		# SC2154: var is referenced but not assigned.
587		# SC2164: Use cd ... || exit in case cd fails.
588		# SC2174: When used with -p, -m only applies to the deepest directory.
589		# SC2178: Variable was used as an array but is now assigned a string.
590		# SC2206: Quote to prevent word splitting/globbing,
591		#         or split robustly with mapfile or read -a.
592		# SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting).
593		# SC2223: This default assignment may cause DoS due to globbing. Quote it.
594		SHCK_EXCLUDE="$SHCK_EXCLUDE,SC1083,SC1090,SC1091,SC2010,SC2015,SC2016,SC2034,SC2046,SC2086,\
595SC2119,SC2120,SC2128,SC2148,SC2153,SC2154,SC2164,SC2174,SC2178,SC2001,SC2206,SC2207,SC2223"
596
597		SHCK_FORMAT="tty"
598		SHCK_APPLY=false
599		SHCH_ARGS="-e $SHCK_EXCLUDE -f $SHCK_FORMAT"
600
601		if ge "$shellcheck_v" 0.4.0; then
602			SHCH_ARGS+=" -x"
603		else
604			echo "shellcheck $shellcheck_v detected, recommended >= 0.4.0."
605		fi
606
607		printf '%s\n' "${files[@]}" | xargs -P$(nproc) -n1 shellcheck $SHCH_ARGS &> shellcheck.log
608		if [[ -s shellcheck.log ]]; then
609			echo " Bash shellcheck errors detected!"
610
611			cat shellcheck.log
612			if $SHCK_APPLY; then
613				git apply shellcheck.log
614				echo "Bash errors were automatically corrected."
615				echo "Please remember to add the changes to your commit."
616			fi
617			rc=1
618		else
619			echo " OK"
620		fi
621		rm -f shellcheck.log
622	else
623		echo "You do not have shellcheck installed so your Bash static analysis is not being performed!"
624	fi
625
626	return $rc
627}
628
629function check_changelog() {
630	local rc=0
631
632	# Check if any of the public interfaces were modified by this patch.
633	# Warn the user to consider updating the changelog any changes
634	# are detected.
635	echo -n "Checking whether CHANGELOG.md should be updated..."
636	staged=$(git diff --name-only --cached .)
637	working=$(git status -s --porcelain --ignore-submodules | grep -iv "??" | awk '{print $2}')
638	files="$staged $working"
639	if [[ "$files" = " " ]]; then
640		files=$(git diff-tree --no-commit-id --name-only -r HEAD)
641	fi
642
643	has_changelog=0
644	for f in $files; do
645		if [[ $f == CHANGELOG.md ]]; then
646			# The user has a changelog entry, so exit.
647			has_changelog=1
648			break
649		fi
650	done
651
652	needs_changelog=0
653	if [ $has_changelog -eq 0 ]; then
654		for f in $files; do
655			if [[ $f == include/spdk/* ]] || [[ $f == scripts/rpc.py ]] || [[ $f == etc/* ]]; then
656				echo ""
657				echo -n "$f was modified. Consider updating CHANGELOG.md."
658				needs_changelog=1
659			fi
660		done
661	fi
662
663	if [ $needs_changelog -eq 0 ]; then
664		echo " OK"
665	else
666		echo ""
667	fi
668
669	return $rc
670}
671
672function check_json_rpc() {
673	local rc=0
674
675	echo -n "Checking that all RPCs are documented..."
676	while IFS='"' read -r _ rpc _; do
677		if ! grep -q "^### $rpc" doc/jsonrpc.md; then
678			echo "Missing JSON-RPC documentation for ${rpc}"
679			rc=1
680		fi
681	done < <(git grep -h -E "^SPDK_RPC_REGISTER\(" ':!test/*' ':!examples/*')
682
683	if [ $rc -eq 0 ]; then
684		echo " OK"
685	fi
686	return $rc
687}
688
689function check_markdown_format() {
690	local rc=0
691
692	if hash mdl 2> /dev/null; then
693		echo -n "Checking markdown files format..."
694		mdl -g -s $rootdir/mdl_rules.rb . > mdl.log || true
695		if [ -s mdl.log ]; then
696			echo " Errors in .md files detected:"
697			cat mdl.log
698			rc=1
699		else
700			echo " OK"
701		fi
702		rm -f mdl.log
703	else
704		echo "You do not have markdownlint installed so .md files not being checked!"
705	fi
706
707	return $rc
708}
709
710function check_rpc_args() {
711	local rc=0
712
713	echo -n "Checking rpc.py argument option names..."
714	grep add_argument scripts/rpc.py | $GNU_GREP -oP "(?<=--)[a-z0-9\-\_]*(?=\')" | grep "_" > badargs.log
715
716	if [[ -s badargs.log ]]; then
717		echo "rpc.py arguments with underscores detected!"
718		cat badargs.log
719		echo "Please convert the underscores to dashes."
720		rc=1
721	else
722		echo " OK"
723	fi
724	rm -f badargs.log
725	return $rc
726}
727
728function get_files_for_lic() {
729	local f_shebang="" f_suffix=() f_type=() f_all=() exceptions=""
730
731	f_shebang+="bash|"
732	f_shebang+="make|"
733	f_shebang+="perl|"
734	f_shebang+="python|"
735	f_shebang+="sh"
736
737	f_suffix+=("*.c")
738	f_suffix+=("*.cpp")
739	f_suffix+=("*.h")
740	f_suffix+=("*.go")
741	f_suffix+=("*.mk")
742	f_suffix+=("*.pl")
743	f_suffix+=("*.py")
744	f_suffix+=("*.sh")
745	f_suffix+=("*.yaml")
746
747	f_type+=("*Dockerfile")
748	f_type+=("*Makefile")
749
750	# Exclude files that may match the above types but should not
751	# fall under SPDX check.
752	exceptions+="include/linux|"
753	exceptions+="include/spdk/queue_extras.h"
754
755	mapfile -t f_all < <(
756		git ls-files "${f_suffix[@]}" "${f_type[@]}"
757		git grep -lE "^#!.*($f_shebang)"
758	)
759
760	printf '%s\n' "${f_all[@]}" | sort -u | grep -vE "$exceptions"
761}
762
763function check_spdx_lic() {
764	local files_missing_license_header=() hint=()
765	local rc=0
766
767	hint+=("SPDX-License-Identifier: BSD-3-Clause")
768	hint+=("All rights reserved.")
769
770	printf 'Checking SPDX-license...'
771
772	mapfile -t files_missing_license_header < <(
773		grep -LE "SPDX-License-Identifier:.+" $(get_files_for_lic)
774	)
775
776	if ((${#files_missing_license_header[@]} > 0)); then
777		printf '\nFollowing files are missing SPDX-license header:\n'
778		printf '  @%s\n' "${files_missing_license_header[@]}"
779		printf '\nExample:\n'
780		printf '  #  %s\n' "${hint[@]}"
781		return 1
782	fi
783
784	printf 'OK\n'
785}
786
787function get_diffed_files() {
788	# Get files where changes are meant to be committed
789	git diff --name-only HEAD HEAD~1
790	# Get files from staging
791	git diff --name-only --cached HEAD
792	git diff --name-only HEAD
793}
794
795function get_diffed_dups() {
796	local files=("$@") diff=() _diff=()
797
798	# Sort and get rid of duplicates from the main list
799	mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u)
800	# Get staged|committed files
801	mapfile -t diff < <(get_diffed_files | sort -u)
802
803	if [[ ! -v CHECK_FORMAT_ONLY_DIFF ]]; then
804		# Just return the main list
805		printf '%s\n' "${files[@]}"
806		return 0
807	fi
808
809	if ((${#diff[@]} > 0)); then
810		# Check diff'ed files against the main list to see if they are a subset
811		# of it. If yes, then we return the duplicates which are the files that
812		# should be committed, modified.
813		mapfile -t _diff < <(
814			printf '%s\n' "${diff[@]}" "${files[@]}" | sort | uniq -d
815		)
816		if ((${#_diff[@]} > 0)); then
817			printf '%s\n' "${_diff[@]}"
818			return 0
819		fi
820	fi
821}
822
823rc=0
824
825check_permissions || rc=1
826check_c_style || rc=1
827
828GIT_VERSION=$(git --version | cut -d' ' -f3)
829
830if version_lt "1.9.5" "${GIT_VERSION}"; then
831	# git <1.9.5 doesn't support pathspec magic exclude
832	echo " Your git version is too old to perform all tests. Please update git to at least 1.9.5 version..."
833	exit $rc
834fi
835
836check_comment_style || rc=1
837check_markdown_format || rc=1
838check_spaces_before_tabs || rc=1
839check_trailing_whitespace || rc=1
840check_forbidden_functions || rc=1
841check_cunit_style || rc=1
842check_eof || rc=1
843check_posix_includes || rc=1
844check_naming_conventions || rc=1
845check_include_style || rc=1
846check_opts_structs || rc=1
847check_attr_packed || rc=1
848check_python_style || rc=1
849check_bash_style || rc=1
850check_bash_static_analysis || rc=1
851check_changelog || rc=1
852check_json_rpc || rc=1
853check_rpc_args || rc=1
854check_spdx_lic || rc=1
855
856exit $rc
857