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