From nobody Wed Apr 1 22:13:36 2026 Received: from mail-wm1-f45.google.com (mail-wm1-f45.google.com [209.85.128.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CEC402E717B for ; Wed, 1 Apr 2026 14:48:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775054891; cv=none; b=g7eRHuY0UrP93Fs/mtaohfSlQdl1OkCALFIiqW1CvJOXvJcx6xZ3oSA8ZTt0ad0rZv4akAXkADMz5/G/oxAcieH7PwZVEAc1y8tq+msSd0y/+5bZB/xSHHxoLtQy2wV8QITLUIzfq3GflHN/k9RaCWkDu59S9DAoP8TkYAXDE18= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775054891; c=relaxed/simple; bh=1a0j+/2frOMbReA062D1KtmtEeL79FEdNZKmMUEzwN4=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UvxTIUj+/AiO4t0EnIBgdMQqT6Cr1SQmg8RE0XqBsrub9qepuOE0wu4C4LFSvhU0QvvEdhGUtAvbZNRTkGaqrUuXtBAs4G5d/+XLCoBvUZK+2bQfB2Axihdaq2pTClBDwXfgydU5fhi0UXpF/KNTYoK6Vd/vZ0MTl1360ohyycY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=NH9cP3O0; arc=none smtp.client-ip=209.85.128.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="NH9cP3O0" Received: by mail-wm1-f45.google.com with SMTP id 5b1f17b1804b1-48541edecf9so83652545e9.1 for ; Wed, 01 Apr 2026 07:48:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775054885; x=1775659685; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to; bh=dJuWWxVJUNEWRRvJz/eTvBx/v65U94aqJB5MtvtEAdc=; b=NH9cP3O0LCHBM53+SobmEx/RSRU0otzubw5X6JfamKnb4G2cn8tsU4wOSdUdINeXoy eIsJ8FCZXanucOORbLwq2q9/NZVb0jgVGBrh6/MOnDFOf54j4i02Aznqnqo4DUBlNfQL 2m9ljnnT6ble5Erq7hYgFfs4T11ollz4As7Yl2OQFIBmjU7bH7MTUnhPDq+bzl5seJ8D FAnugfaDF+b7ZL1YgP8xFuW94S2NhckSDjjzmScuL6ALk/4BvkIdQru/bWKg4QuWnfCH ffnbRPk0oN/iUv8ttTdqLgQRzxlcIjw2AJRvWpj4tJWw1aiTSHFEOWOinzKGdSVmzAVi AvjA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775054885; x=1775659685; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=dJuWWxVJUNEWRRvJz/eTvBx/v65U94aqJB5MtvtEAdc=; b=e27EsHsP8j0RmA21ozlK102nt6kCnK1JyGTtfUXnqyba8XG7fpHC5BoIXO6zO/n9gW z/qSq2PfAma9kNJd4C22jik8QFWzYEYQCfLoO67XPdpiOQgwmH32U47+YBD5UbRQE8HZ gqnAMTptPjAWtrVe/x53jNU7DYgSOA1yCjw72R3aNnxmDW+noC79DuXQGnq0XgFBQhWR qaaWI1KGN+M9LRS+JfDM7ZFjV0hrjWSyb1n/8PhkHYTMk+RD7vTdjeDoPtwkrRpFdL4c Qjhl+Us4tDlKvLbZa77OZDdVRNvIbmf7Q1P2x7VbudFD7v5pBgwdmwYFf40lTrz/3QVS p3zw== X-Gm-Message-State: AOJu0Yyf0w7f2T88ewP9uDWAWN+JpdYJ/PMJirnULZim6kENP8ZVo7uo zOlE8o9whAcFsGHpZJMNgxA8tC7f2zBwW2fEKJBzTHhncEiVqSQZ/xmeuTwO0GxZL7A= X-Gm-Gg: ATEYQzz6bhML9XA+qoXxz4uyrzv0efloYa9HvfcLtYtqTtph4eGtbsXSGwmsUG6eTvK 2XDbLvg86+up9Pq6MfIUQCNM3w6DUayreT2S+W7Hh18lmoDIKj77NSQ5eCwPULoAJFNcsd1fVaj gpO5t+HJWfKPKd3/NBQzOl5hN68kOytitbCb/P1j+BY6tm3uRAkaKuRd5pl5jGb4n+C5lNqsg6e Wi6Lv5zsmmGssI07bqIKHMTkJeQWYwnu7kZFIE+LC0ky5bkUrjNuctqiYqLOKAiPwv8UeaisisG wcaqy77evXniK36Q1iiTAce05k5fmK7bcsqeXc2GBmx2khzTysNPcFl9oUNRnLYyLwJ3dIIDU9b 2x8DVBsq4+g8gIg/MTPze/EqXB5JKGJVeLuZSUD6WTuCkYgjkorcKgsPO/GlP0bsj9yHRPsbgSC kr42swpiQ3XQt7sajD+QCVV7VWfvj9eoIztMtNhW+S8BMfO4AZK8Ev/uhxO7UgA8WEGFYVPicP X-Received: by 2002:a05:600c:4704:b0:486:fcc7:d6a with SMTP id 5b1f17b1804b1-4888358931fmr64125485e9.13.1775054884345; Wed, 01 Apr 2026 07:48:04 -0700 (PDT) Received: from fedorarm (net-2-37-83-250.cust.vodafonedsl.it. [2.37.83.250]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4887c8b6230sm45753505e9.24.2026.04.01.07.48.03 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 07:48:03 -0700 (PDT) From: Guido De Rossi To: LKML Subject: [PATCH 2/2] checkpatch: rewrite in Python Date: Wed, 1 Apr 2026 16:47:23 +0200 Message-ID: <20260401144723.44406-3-guido.derossi91@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260401144723.44406-1-guido.derossi91@gmail.com> References: <20260401144723.44406-1-guido.derossi91@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add scripts/checkpatch.py, a Python 3.6+ rewrite of scripts/checkpatch.pl. This continues the effort to deprecate Perl in kernel scripts, following scripts/get_maintainer.py. The Python version implements the core checking infrastructure and the most commonly triggered checks. It is designed as a drop-in replacement with identical CLI options and output format. Implemented checks include: - Commit message validation (sign-off, fixes tag, line length, diff content, gerrit change-id, git commit references) - Whitespace (trailing, DOS endings, tabs vs spaces, space before tab) - SPDX license tags - Line length limits with URL/string exceptions - Code style (brace placement, spacing around operators, function parentheses, if/while/for spacing) - API usage (volatile, printk levels, BUG variants, deprecated APIs, strcpy/strlcpy/strncpy, udelay/msleep, jiffies comparison) - Type checks (new typedefs, sizeof usage, CamelCase) - File checks (execute permissions, embedded filename, FSF address) - Spelling/typo detection via spelling.txt and optional codespell Checks requiring full C statement context analysis (ctx_statement_block, annotate_values, operator spacing, macro analysis, brace balancing) are scaffolded but simplified. These represent the remaining checks and will be completed incrementally. Benchmark comparison (wall time): Mode Perl Python Speedup --------------------------------------------------- core.c (10.9k lines) 9.0s 4.1s 2.2x dev.c (13.3k lines) 11.7s 4.8s 2.4x super.c (7.6k lines) 7.3s 3.3s 2.2x 5 files (~50k lines) 40.7s 16.6s 2.5x Patch mode (1 commit) 2.8s 2.7s 1.0x Git mode (5 commits) 14.3s 11.8s 1.2x Signed-off-by: Guido De Rossi --- scripts/checkpatch.py | 2417 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2417 insertions(+) create mode 100755 scripts/checkpatch.py diff --git a/scripts/checkpatch.py b/scripts/checkpatch.py new file mode 100755 index 000000000000..712aa373c03c --- /dev/null +++ b/scripts/checkpatch.py @@ -0,0 +1,2417 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# +# (c) 2001, Dave Jones. (the file handling bit) +# (c) 2005, Joel Schopp (the ugly bit) +# (c) 2007,2008, Andy Whitcroft (new conditions, test sui= te) +# (c) 2008-2010 Andy Whitcroft +# +# Python rewrite of scripts/checkpatch.pl + +import argparse +import os +import re +import shutil +import subprocess +import sys + +P =3D os.path.basename(sys.argv[0]) +D =3D os.path.dirname(os.path.abspath(sys.argv[0])) + +V =3D '0.32' + +# ---- Global options with defaults ---- +quiet =3D 0 +verbose =3D False +verbose_messages =3D {} +verbose_emitted =3D {} +tree =3D True +chk_signoff =3D True +chk_fixes_tag =3D True +chk_patch =3D True +tst_only =3D None +emacs =3D False +terse =3D False +showfile =3D False +file_mode =3D False +git_mode =3D False +git_commits =3D {} +check =3D False +check_orig =3D False +summary =3D True +mailback =3D False +summary_file =3D False +show_types =3D False +list_types =3D False +fix =3D False +fix_inplace =3D False +root =3D None +gitroot =3D os.environ.get('GIT_DIR', '.git') +debug =3D {} +camelcase =3D {} +use_type =3D {} +use_list =3D [] +ignore_type =3D {} +ignore_list =3D [] +max_line_length =3D 100 +min_conf_desc_length =3D 4 +spelling_file =3D os.path.join(D, 'spelling.txt') +codespell =3D False +codespellfile =3D '/usr/share/codespell/dictionary.txt' +user_codespellfile =3D '' +conststructsfile =3D os.path.join(D, 'const_structs.checkpatch') +docsfile =3D os.path.join(D, '..', 'Documentation', 'dev-tools', 'checkpat= ch.rst') +typedefsfile =3D None +color =3D 'auto' +allow_c99_comments =3D True +git_command =3D 'export LANGUAGE=3Den_US.UTF-8; git' +tabsize =3D 8 +CONFIG_ =3D 'CONFIG_' +configuration_file =3D '.checkpatch.conf' + +maybe_linker_symbol =3D {} + +# ---- ANSI color codes ---- +RED =3D '\033[31m' +YELLOW =3D '\033[33m' +GREEN =3D '\033[32m' +BLUE =3D '\033[34m' +RESET =3D '\033[0m' + +# ---- Regex patterns (matching Perl exactly) ---- + +Ident =3D r'[A-Za-z_][A-Za-z\d_]*(?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)*' +Storage =3D r'(?:extern|static|asmlinkage)' +Sparse =3D r'(?:__user|__kernel|__force|__iomem|__must_check|__kprobes|__r= ef|__refconst|__refdata|__rcu|__private)' +InitAttributePrefix =3D r'__(?:mem|cpu|dev|net_|)' +InitAttributeData =3D InitAttributePrefix + r'(?:initdata\b)' +InitAttributeConst =3D InitAttributePrefix + r'(?:initconst\b)' +InitAttributeInit =3D InitAttributePrefix + r'(?:init\b)' +InitAttribute =3D f'(?:{InitAttributeData}|{InitAttributeConst}|{InitAttri= buteInit})' + +Attribute =3D (r'(?:const|volatile|__percpu|__nocast|__safe|__bitwise|__pa= cked__|__packed2__|' + r'__naked|__maybe_unused|__always_unused|__noreturn|__used|__= cold|__pure|' + r'__noclone|__deprecated|__read_mostly|__ro_after_init|__kpro= bes|' + + InitAttribute + r'|' + r'__aligned\s*\(.*\)|____cacheline_aligned|____cacheline_alig= ned_in_smp|' + r'____cacheline_internodealigned_in_smp|__weak|' + r'__alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\))') + +Inline =3D r'(?:inline|__always_inline|noinline|__inline|__inline__)' +Member =3D f'(?:->{Ident}|\\.{Ident}|\\[[^\\]]*\\])' +Lval =3D f'(?:{Ident}(?:{Member})*)' + +Int_type =3D r'(?:[iI])?(?:llu|ull|ll|lu|ul|l|u)' +Binary =3D r'(?:[iI])?0[bB][01]+(?:' + Int_type + r')?' +Hex =3D r'(?:[iI])?0[xX][0-9a-fA-F]+(?:' + Int_type + r')?' +Int =3D r'[0-9]+(?:' + Int_type + r')?' +Octal =3D r'0[0-7]+(?:' + Int_type + r')?' +String =3D r'(?:\b[Lu])?"[X\t]*"' +Float_hex =3D r'(?:[iI])?0[xX][0-9a-fA-F]+[pP]-?[0-9]+[fFlL]?' +Float_dec =3D r'(?:[iI])?(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:[eE]-?[0-9]+)= ?[fFlL]?' +Float_int =3D r'(?:[iI])?[0-9]+[eE]-?[0-9]+[fFlL]?' +Float =3D f'(?:{Float_hex}|{Float_dec}|{Float_int})' +Constant =3D f'(?:{Float}|{Binary}|{Octal}|{Hex}|{Int})' +Assignment =3D r'(?:\*=3D|/=3D|%=3D|\+=3D|-=3D|<<=3D|>>=3D|&=3D|\^=3D|\|= =3D|=3D)' +Compare =3D r'(?:<=3D|>=3D|=3D=3D|!=3D|<|(?)' +Arithmetic =3D r'(?:\+|-|\*|/|%)' +Operators =3D r'(?:<=3D|>=3D|=3D=3D|!=3D|=3D>|->|<<|>>|<|>|!|~|&&|\|\||,|\= ^|\+\+|--|&|\||' + Arithmetic + r')' + +c90_Keywords =3D r'(?:do|for|while|if|else|return|goto|continue|switch|def= ault|case|break)' + +NON_ASCII_UTF8 =3D (r'(?:[\xC2-\xDF][\x80-\xBF]' + r'|\xE0[\xA0-\xBF][\x80-\xBF]' + r'|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}' + r'|\xED[\x80-\x9F][\x80-\xBF]' + r'|\xF0[\x90-\xBF][\x80-\xBF]{2}' + r'|[\xF1-\xF3][\x80-\xBF]{3}' + r'|\xF4[\x80-\x8F][\x80-\xBF]{2})') +UTF8 =3D r'(?:[\x09\x0A\x0D\x20-\x7E]|' + NON_ASCII_UTF8 + r')' + +typeC99Typedefs =3D r'(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t' +typeOtherOSTypedefs =3D r'(?:u_(?:char|short|int|long)|u(?:nchar|short|int= |long))' +typeKernelTypedefs =3D r'(?:(?:__)?(?:u|s|be|le)(?:8|16|32|64)|atomic_t)' +typeStdioTypedefs =3D r'(?:FILE)' +typeTypedefs =3D f'(?:{typeC99Typedefs}\\b|{typeOtherOSTypedefs}\\b|{typeK= ernelTypedefs}\\b|{typeStdioTypedefs}\\b)' + +zero_initializer =3D r'(?:(?:0[xX])?0+(?:' + Int_type + r')?|NULL|false)\b' + +logFunctions =3D (r'(?:printk(?:_ratelimited|_once|_deferred_once|_deferre= d|)|' + r'(?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warnin= g|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)|' + r'TP_printk|WARN(?:_RATELIMIT|_ONCE|)|panic|' + r'MODULE_[A-Z_]+|seq_vprintf|seq_printf|seq_puts)') + +allocFunctions =3D (r'(?:(?:(?:devm_)?(?:kv|k|v)[czm]alloc(?:_array)?(?:_n= ode)?|' + r'kstrdup(?:_const)?|kmemdup(?:_nul)?)|' + r'(?:\w+)?alloc_skb(?:_ip_align)?|dma_alloc_coherent)') + +signature_tags =3D (r'(?:Signed-off-by:|Co-developed-by:|Acked-by:|Tested-= by:|' + r'Reviewed-by:|Reported-by:|Suggested-by:|To:|Cc:)') + +link_tags =3D ['Link', 'Closes'] +link_tags_search =3D '(?:' + '|'.join(t + ':' for t in link_tags) + ')' +link_tags_print =3D ' or '.join("'" + t + ":'" for t in link_tags) + +tracing_logging_tags =3D (r'(?:[=3D\-]*>|<[=3D\-]*|\[|\]|start|called|ente= red|entry|enter|in|' + r'inside|here|begin|exit|end|done|leave|completed|= out|return|[\.\!:\s]*)') + +dev_id_types =3D r'\b[a-z]\w*_device_id\b' + +obsolete_archives =3D (r'(?:freedesktop\.org/archives/dri-devel|' + r'lists\.infradead\.org|lkml\.org|mail-archive\.com|' + r'mailman\.alsa-project\.org/pipermail|marc\.info|' + r'ozlabs\.org/pipermail|spinics\.net)') + +typeListMisordered =3D [ + r'char\s+(?:un)?signed', + r'int\s+(?:(?:un)?signed\s+)?short\s', + r'int\s+short(?:\s+(?:un)?signed)', + r'short\s+int(?:\s+(?:un)?signed)', + r'(?:un)?signed\s+int\s+short', + r'short\s+(?:un)?signed', + r'long\s+int\s+(?:un)?signed', + r'int\s+long\s+(?:un)?signed', + r'long\s+(?:un)?signed\s+int', + r'int\s+(?:un)?signed\s+long', + r'int\s+(?:un)?signed', + r'int\s+long\s+long\s+(?:un)?signed', + r'long\s+long\s+int\s+(?:un)?signed', + r'long\s+long\s+(?:un)?signed\s+int', + r'long\s+long\s+(?:un)?signed', + r'long\s+(?:un)?signed', +] + +typeList =3D [ + r'void', + r'(?:(?:un)?signed\s+)?char', + r'(?:(?:un)?signed\s+)?short\s+int', + r'(?:(?:un)?signed\s+)?short', + r'(?:(?:un)?signed\s+)?int', + r'(?:(?:un)?signed\s+)?long\s+int', + r'(?:(?:un)?signed\s+)?long\s+long\s+int', + r'(?:(?:un)?signed\s+)?long\s+long', + r'(?:(?:un)?signed\s+)?long', + r'(?:un)?signed', + r'float', + r'double', + r'bool', + f'struct\\s+{Ident}', + f'union\\s+{Ident}', + f'enum\\s+{Ident}', + f'{Ident}_t', + f'{Ident}_handler', + f'{Ident}_handler_fn', +] + typeListMisordered + +C90_int_types =3D (r'(?:long\s+long\s+int\s+(?:un)?signed|long\s+long\s+(?= :un)?signed\s+int|' + r'long\s+long\s+(?:un)?signed|(?:(?:un)?signed\s+)?long\s= +long\s+int|' + r'(?:(?:un)?signed\s+)?long\s+long|int\s+long\s+long\s+(?= :un)?signed|' + r'int\s+(?:(?:un)?signed\s+)?long\s+long|' + r'long\s+int\s+(?:un)?signed|long\s+(?:un)?signed\s+int|' + r'long\s+(?:un)?signed|(?:(?:un)?signed\s+)?long\s+int|' + r'(?:(?:un)?signed\s+)?long|int\s+long\s+(?:un)?signed|' + r'int\s+(?:(?:un)?signed\s+)?long|' + r'int\s+(?:un)?signed|(?:(?:un)?signed\s+)?int)') + +typeListFile =3D [] +typeListWithAttr =3D typeList + [ + f'struct\\s+{InitAttribute}\\s+{Ident}', + f'union\\s+{InitAttribute}\\s+{Ident}', +] + +modifierList =3D [r'fastcall'] +modifierListFile =3D [] + +mode_permission_funcs =3D [ + ("module_param", 3), + ("module_param_(?:array|named|string)", 4), + ("module_param_array_named", 5), + ("debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t= |bool|blob|regset32|u32_array)", 2), + ("proc_create(?:_data|)", 2), + ("(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2), + ("IIO_DEV_ATTR_[A-Z_]+", 1), + ("SENSOR_(?:DEVICE_|)ATTR_2", 2), + ("SENSOR_TEMPLATE(?:_2|)", 3), + ("__ATTR", 2), +] + +word_pattern =3D r'\b[A-Z]?[a-z]{2,}\b' + +mode_perms_search =3D '(?:' + '|'.join(e[0] for e in mode_permission_funcs= ) + ')' + +deprecated_apis =3D { + "kmap": "kmap_local_page", + "kunmap": "kunmap_local", + "kmap_atomic": "kmap_local_page", + "kunmap_atomic": "kunmap_local", + "DEFINE_IDR": "DEFINE_XARRAY", + "idr_init": "xa_init", + "idr_init_base": "xa_init_flags", +} + +deprecated_apis_search =3D '(?:' + '|'.join(re.escape(k) for k in deprecat= ed_apis) + ')' + +mode_perms_world_writable =3D r'(?:S_IWUGO|S_IWOTH|S_IRWXUGO|S_IALLUGO|0[0= -7][0-7][2367])' + +mode_permission_string_types =3D { + "S_IRWXU": 0o700, "S_IRUSR": 0o400, "S_IWUSR": 0o200, "S_IXUSR": 0o100, + "S_IRWXG": 0o070, "S_IRGRP": 0o040, "S_IWGRP": 0o020, "S_IXGRP": 0o010, + "S_IRWXO": 0o007, "S_IROTH": 0o004, "S_IWOTH": 0o002, "S_IXOTH": 0o001, + "S_IRWXUGO": 0o777, "S_IRUGO": 0o444, "S_IWUGO": 0o222, "S_IXUGO": 0o1= 11, +} + +single_mode_perms_string_search =3D '(?:' + '|'.join(mode_permission_strin= g_types.keys()) + ')' +multi_mode_perms_string_search =3D single_mode_perms_string_search + r'(?:= \s*\|\s*' + single_mode_perms_string_search + r')*' + +allowed_asm_includes =3D r'(?:irq|memory|time|reboot)' + +allow_repeated_words =3D {'add', 'added', 'bad', 'be'} + +# ---- Dynamically built type patterns ---- +Modifier =3D '' +BasicType =3D '' +NonptrType =3D '' +NonptrTypeMisordered =3D '' +NonptrTypeWithAttr =3D '' +Type =3D '' +TypeMisordered =3D '' +Declare =3D '' +DeclareMisordered =3D '' + +def build_types(): + global Modifier, BasicType, NonptrType, NonptrTypeMisordered, NonptrTy= peWithAttr + global Type, TypeMisordered, Declare, DeclareMisordered + + mods =3D '(?:' + '|'.join(modifierList + modifierListFile) + ')' + all_types =3D '(?:' + '|'.join(typeList + typeListFile) + ')' + mis =3D '(?:' + '|'.join(typeListMisordered) + ')' + allWithAttr =3D '(?:' + '|'.join(typeListWithAttr) + ')' + + Modifier =3D f'(?:{Attribute}|{Sparse}|{mods})' + BasicType =3D f'(?:{typeTypedefs}\\b|(?:{all_types})\\b)' + NonptrType =3D (f'(?:(?:{Modifier}\\s+|const\\s+)*' + f'(?:(?:typeof|__typeof__)\\s*\\([^\\)]*\\)|{typeTypedef= s}\\b|(?:{all_types})\\b)' + f'(?:\\s+{Modifier}|\\s+const)*)') + NonptrTypeMisordered =3D (f'(?:(?:{Modifier}\\s+|const\\s+)*' + f'(?:(?:{mis})\\b)' + f'(?:\\s+{Modifier}|\\s+const)*)') + NonptrTypeWithAttr =3D (f'(?:(?:{Modifier}\\s+|const\\s+)*' + f'(?:(?:typeof|__typeof__)\\s*\\([^\\)]*\\)|{typ= eTypedefs}\\b|(?:{allWithAttr})\\b)' + f'(?:\\s+{Modifier}|\\s+const)*)') + Type =3D (f'(?:{NonptrType}' + f'(?:(?:\\s|\\*|\\[\\])+\\s*const|(?:\\s|\\*\\s*(?:const\\s*)?= |\\[\\])+|(?:\\s*\\[\\s*\\])+){{0,4}}' + f'(?:\\s+{Inline}|\\s+{Modifier})*)') + TypeMisordered =3D (f'(?:{NonptrTypeMisordered}' + f'(?:(?:\\s|\\*|\\[\\])+\\s*const|(?:\\s|\\*\\s*(?:c= onst\\s*)?|\\[\\])+|(?:\\s*\\[\\s*\\])+){{0,4}}' + f'(?:\\s+{Inline}|\\s+{Modifier})*)') + Declare =3D f'(?:(?:{Storage}\\s+(?:{Inline}\\s+)?)?{Type})' + DeclareMisordered =3D f'(?:(?:{Storage}\\s+(?:{Inline}\\s+)?)?{TypeMis= ordered})' + +build_types() + +Typecast =3D f'(?:\\s*(?:\\(\\s*{NonptrType}\\s*\\)){{0,1}}\\s*)' + +# These require recursive regex which Python doesn't have natively. +# We approximate balanced_parens with a non-recursive depth-limited versio= n. +balanced_parens =3D r'(\((?:[^()]*|\((?:[^()]*|\([^()]*\))*\))*\))' +LvalOrFunc =3D f'(?:(?:[&*]\\s*)?{Lval})\\s*(?:{balanced_parens}{{0,1}})\\= s*' +FuncArg =3D f'(?:{Typecast}{{0,1}}(?:{LvalOrFunc}|{Constant}|{String}))' + +declaration_macros =3D (f'(?:(?:{Storage}\\s+)?(?:[A-Z_][A-Z0-9]*_){{0,2}}= (?:DEFINE|DECLARE)(?:_[A-Z0-9]+){{1,6}}\\s*\\(|' + f'(?:{Storage}\\s+)?[HLP]?LIST_HEAD\\s*\\(|' + r'(?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_S= TACK\s*\(|' + f'(?:{Storage}\\s+)?(?:XA_STATE|XA_STATE_ORDER)\\s*\= \()') + +# Comment character used to sanitize lines +COMMENT_CHAR =3D chr(1) # \x01 used like Perl's $; + +# ---- Utility functions ---- + +def which(bin_name): + path =3D shutil.which(bin_name) + return path if path else "" + +def which_conf(conf): + for path_dir in ['.', os.environ.get('HOME', ''), '.scripts']: + p =3D os.path.join(path_dir, conf) + if os.path.exists(p): + return p + return "" + +def top_of_kernel_tree(root_path): + checks =3D ["COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", + "README", "Documentation", "arch", "include", "drivers", + "fs", "init", "ipc", "kernel", "lib", "scripts"] + for c in checks: + if not os.path.exists(os.path.join(root_path, c)): + return False + return True + +def trim(s): + return s.strip() + +def ltrim(s): + return s.lstrip() + +def rtrim(s): + return s.rstrip() + +def expand_tabs(s): + res =3D '' + n =3D 0 + for c in s: + if c =3D=3D '\t': + res +=3D ' ' + n +=3D 1 + while n % tabsize !=3D 0: + res +=3D ' ' + n +=3D 1 + else: + res +=3D c + n +=3D 1 + return res + +def copy_spacing(s): + return re.sub(r'[^\t]', ' ', s) + +def line_stats(line): + line =3D line[1:] if line else '' # drop diff marker + line =3D expand_tabs(line) + m =3D re.match(r'^(\s*)', line) + white =3D len(m.group(1)) if m else 0 + return (len(line), white) + +def cat_vet(vet): + res =3D '' + for c in vet: + if c =3D=3D '\t': + res +=3D c + elif ord(c) < 32 or ord(c) =3D=3D 127: + res +=3D '^' + chr(ord(c) + 64) if ord(c) < 127 else '^?' + else: + res +=3D c + res +=3D '$' + return res + +def tabify(leading): + spaces =3D ' ' * tabsize + while spaces in leading: + leading =3D leading.replace(spaces, '\t', 1) + return leading + +def string_find_replace(string, find, replace): + return re.sub(find, replace, string) + +def deparenthesize(string): + if string is None: + return "" + while re.match(r'^\s*\(.*\)\s*$', string, re.DOTALL): + string =3D re.sub(r'^\s*\(\s*', '', string) + string =3D re.sub(r'\s*\)\s*$', '', string) + string =3D re.sub(r'\s+', ' ', string) + return string + +def get_edit_distance(str1, str2): + str1 =3D str1.lower().replace('-', '') + str2 =3D str2.lower().replace('-', '') + len1, len2 =3D len(str1), len(str2) + d =3D [[0] * (len2 + 1) for _ in range(len1 + 1)] + for i in range(len1 + 1): + d[i][0] =3D i + for j in range(len2 + 1): + d[0][j] =3D j + for i in range(1, len1 + 1): + for j in range(1, len2 + 1): + if str1[i-1] =3D=3D str2[j-1]: + d[i][j] =3D d[i-1][j-1] + else: + d[i][j] =3D 1 + min(d[i][j-1], d[i-1][j], d[i-1][j-1]) + return d[len1][len2] + +def find_standard_signature(sign_off): + standard =3D ['Signed-off-by:', 'Co-developed-by:', 'Acked-by:', 'Test= ed-by:', + 'Reviewed-by:', 'Reported-by:', 'Suggested-by:'] + for sig in standard: + if get_edit_distance(sign_off, sig) <=3D 2: + return sig + return "" + +def perms_to_octal(string): + string =3D string.strip() + if re.match(r'^\s*0[0-7]{3}\s*$', string): + return string.strip() + to_val =3D 0 + for m in re.finditer(r'\b(' + single_mode_perms_string_search + r')\b'= , string): + match =3D m.group(1) + if match in mode_permission_string_types: + to_val |=3D mode_permission_string_types[match] + return f'{to_val:04o}' + +def is_userspace(realfile): + return bool(re.match(r'^tools/', realfile) or re.match(r'^scripts/', r= ealfile)) + +def exclude_global_initialisers(realfile): + return bool(re.match(r'^tools/testing/selftests/bpf/progs/.*\.c$', rea= lfile) or + re.match(r'^samples/bpf/.*_kern\.c$', realfile) or + re.search(r'/bpf/.*\.bpf\.c$', realfile)) + +# ---- Sanitize line (replace comments/strings with placeholders) ---- + +_sanitise_quote =3D '' + +def sanitise_line_reset(in_comment=3DFalse): + global _sanitise_quote + _sanitise_quote =3D '*/' if in_comment else '' + +def sanitise_line(line): + global _sanitise_quote + if not line: + return line + + res =3D list(line[0]) # copy diff marker + rest =3D line[1:] if len(line) > 1 else '' + + off =3D 0 + length =3D len(rest) + while off < length: + c =3D rest[off] + + # Block comments + if _sanitise_quote =3D=3D '' and off + 1 < length and rest[off:off= +2] =3D=3D '/*': + _sanitise_quote =3D '*/' + res.append(COMMENT_CHAR) + res.append(COMMENT_CHAR) + off +=3D 2 + continue + if _sanitise_quote =3D=3D '*/' and off + 1 < length and rest[off:o= ff+2] =3D=3D '*/': + _sanitise_quote =3D '' + res.append(COMMENT_CHAR) + res.append(COMMENT_CHAR) + off +=3D 2 + continue + if _sanitise_quote =3D=3D '' and off + 1 < length and rest[off:off= +2] =3D=3D '//': + _sanitise_quote =3D '//' + res.append('/') + res.append('/') + off +=3D 2 + continue + + # Escaped chars in strings + if _sanitise_quote in ("'", '"') and c =3D=3D '\\' and off + 1 < l= ength: + res.append('X') + res.append('X') + off +=3D 2 + continue + + # Quotes + if c in ("'", '"'): + if _sanitise_quote =3D=3D '': + _sanitise_quote =3D c + res.append(c) + off +=3D 1 + continue + elif _sanitise_quote =3D=3D c: + _sanitise_quote =3D '' + + # Replace content + if _sanitise_quote =3D=3D '*/' and c !=3D '\t': + res.append(COMMENT_CHAR) + elif _sanitise_quote =3D=3D '//' and c !=3D '\t': + res.append(COMMENT_CHAR) + elif _sanitise_quote and _sanitise_quote not in ('*/', '//') and c= !=3D '\t': + res.append('X') + else: + res.append(c) + off +=3D 1 + + if _sanitise_quote =3D=3D '//': + _sanitise_quote =3D '' + + result =3D ''.join(res) + + # Clean up #include paths + m =3D re.match(r'^.\s*\#\s*include\s+<(.*)>', result) + if m: + clean =3D 'X' * len(m.group(1)) + result =3D re.sub(r'<.*>', f'<{clean}>', result, count=3D1) + else: + m =3D re.match(r'^.\s*\#\s*(?:error|warning)\s+(.*)\b', result) + if m: + clean =3D 'X' * len(m.group(1)) + result =3D re.sub(r'(\#\s*(?:error|warning)\s+).*', r'\g<1>' += clean, result, count=3D1) + + if allow_c99_comments: + m =3D re.search(r'(//.*$)', result) + if m: + repl =3D COMMENT_CHAR * len(m.group(1)) + result =3D result[:m.start(1)] + repl + result[m.end(1):] + + return result + +def get_quoted_string(line, rawline): + if not line or not rawline: + return "" + m =3D re.search(String, line) + if not m: + return "" + return rawline[m.start():m.end()] + +# ---- Reporting functions ---- + +report_list =3D [] +cnt_lines =3D 0 +cnt_error =3D 0 +cnt_warn =3D 0 +cnt_chk =3D 0 +clean =3D 1 +prefix =3D '' +rpt_cleaners =3D 0 + +def show_type(msg_type): + msg_type =3D msg_type.upper() + if use_type: + return msg_type in use_type + return msg_type not in ignore_type + +def report(level, msg_type, msg): + global prefix, report_list + if not show_type(msg_type): + return False + if tst_only and tst_only not in msg: + return False + + output =3D '' + if color_enabled: + if level =3D=3D 'ERROR': + output +=3D RED + elif level =3D=3D 'WARNING': + output +=3D YELLOW + else: + output +=3D GREEN + + output +=3D prefix + level + ':' + if show_types: + if color_enabled: + output +=3D BLUE + output +=3D msg_type + ':' + if color_enabled: + output +=3D RESET + output +=3D ' ' + msg + '\n' + + if showfile: + lines =3D output.split('\n', 2) + if len(lines) > 2: + output =3D lines[0] + '\n' + '\n'.join(lines[2:]) + + if terse: + output =3D output.split('\n')[0] + '\n' + + if verbose and msg_type in verbose_messages and msg_type not in verbos= e_emitted: + output +=3D verbose_messages[msg_type] + '\n\n' + verbose_emitted[msg_type] =3D True + + report_list.append(output) + return True + +def ERROR(msg_type, msg): + global clean, cnt_error + if report("ERROR", msg_type, msg): + clean =3D 0 + cnt_error +=3D 1 + return True + return False + +def WARN(msg_type, msg): + global clean, cnt_warn + if report("WARNING", msg_type, msg): + clean =3D 0 + cnt_warn +=3D 1 + return True + return False + +def CHK(msg_type, msg): + global clean, cnt_chk + if check and report("CHECK", msg_type, msg): + clean =3D 0 + cnt_chk +=3D 1 + return True + return False + +# ---- Fix tracking ---- + +fixed =3D [] +fixed_inserted =3D [] +fixed_deleted =3D [] +fixlinenr =3D -1 + +def fix_insert_line(linenr, line): + fixed_inserted.append({'LINENR': linenr, 'LINE': line}) + +def fix_delete_line(linenr, line): + fixed_deleted.append({'LINENR': linenr, 'LINE': line}) + +def fixup_current_range(lines_list, idx, offset, length): + if idx < len(lines_list): + m =3D re.match(r'^(@@ -\d+,\d+ \+)(\d+),(\d+)( @@)', lines_list[id= x]) + if m: + new_o =3D int(m.group(2)) + offset + new_l =3D int(m.group(3)) + length + lines_list[idx] =3D f'{m.group(1)}{new_o},{new_l}{m.group(4)}' + +def fix_inserted_deleted_lines(lines_ref, inserted_ref, deleted_ref): + range_last =3D 0 + delta_offset =3D 0 + old_linenr =3D 0 + new_linenr =3D 0 + next_insert =3D 0 + next_delete =3D 0 + result =3D [] + + for old_line in lines_ref: + save =3D True + line =3D old_line + + if re.match(r'^(?:\+\+\+|---)\s+\S+', line): + delta_offset =3D 0 + elif re.match(r'^@@ -\d+,\d+ \+\d+,\d+ @@', line): + range_last =3D new_linenr + fixup_current_range(result, range_last, delta_offset, 0) if re= sult else None + + while next_delete < len(deleted_ref) and deleted_ref[next_delete][= 'LINENR'] =3D=3D old_linenr: + next_delete +=3D 1 + save =3D False + delta_offset -=3D 1 + + while next_insert < len(inserted_ref) and inserted_ref[next_insert= ]['LINENR'] =3D=3D old_linenr: + result.append(inserted_ref[next_insert]['LINE']) + next_insert +=3D 1 + new_linenr +=3D 1 + delta_offset +=3D 1 + + if save: + result.append(line) + new_linenr +=3D 1 + + old_linenr +=3D 1 + + return result + +# ---- Context analysis functions ---- + +def raw_line(linenr, cnt, rawlines): + offset =3D linenr - 1 + cnt +=3D 1 + line =3D None + while cnt > 0: + if offset >=3D len(rawlines): + return None + line =3D rawlines[offset] + offset +=3D 1 + if line is not None and line.startswith('-'): + continue + cnt -=3D 1 + return line + +def get_stat_real(linenr, lc, rawlines): + stat_real =3D raw_line(linenr, 0, rawlines) + if stat_real is None: + return "" + for count in range(linenr + 1, lc + 1): + rl =3D raw_line(count, 0, rawlines) + if rl is not None: + stat_real +=3D '\n' + rl + return stat_real + +def get_stat_here(linenr, cnt, here, rawlines): + herectx =3D here + '\n' + for n in range(cnt): + rl =3D raw_line(linenr, n, rawlines) + if rl is not None: + herectx +=3D rl + '\n' + return herectx + +def ctx_has_comment(first_line, end_line, rawlines): + """Check if there's a comment in the context around end_line.""" + # Check current, previous, and next lines for // comments + for idx in [end_line - 1, end_line - 2, end_line]: + if 0 <=3D idx < len(rawlines): + m =3D re.search(r'//.*$', rawlines[idx]) + if m: + return True + # Check for inline /* */ comment + if 0 <=3D end_line - 1 < len(rawlines): + if re.search(r'/\*.*\*/', rawlines[end_line - 1]): + return True + # Check for block comment in context + in_comment =3D False + for ln in range(first_line - 1, end_line): + if 0 <=3D ln < len(rawlines): + if '/*' in rawlines[ln]: + in_comment =3D True + if in_comment: + return True + if '*/' in rawlines[ln]: + in_comment =3D False + return False + +def ctx_statement_block(linenr, remain, off, lines, rawlines): + """Extract a statement block from the source.""" + line =3D linenr - 1 + blk =3D '' + soff =3D off + coff =3D off - 1 + coff_set =3D False + loff =3D 0 + ptype =3D '' + level =3D 0 + stack =3D [] + p =3D None + length =3D 0 + + while True: + if not stack: + stack =3D [('', 0)] + + if off >=3D length: + while remain > 0: + if line >=3D len(lines): + break + if lines[line] is not None and lines[line].startswith('-'): + line +=3D 1 + continue + remain -=3D 1 + loff =3D length + blk +=3D (lines[line] if lines[line] is not None else '') = + '\n' + length =3D len(blk) + line +=3D 1 + break + + if off >=3D length: + break + + if level =3D=3D 0 and re.match(r'^.\s*#\s*define', blk[off:]): + level +=3D 1 + ptype =3D '#' + + p_prev =3D p + p =3D blk[off] if off < length else '' + remainder =3D blk[off:] + + # Handle nested #if/#else + if re.match(r'^#\s*(?:ifndef|ifdef|if)\s', remainder): + stack.append((ptype, level)) + elif re.match(r'^#\s*(?:else|elif)\b', remainder): + if len(stack) >=3D 2: + ptype, level =3D stack[-2] + elif re.match(r'^#\s*endif\b', remainder): + if stack: + ptype, level =3D stack.pop() + + if level =3D=3D 0 and p =3D=3D ';': + break + + # else detection + if (level =3D=3D 0 and not coff_set and + (p_prev is None or re.match(r'[\s}+]', str(p_prev))) and + re.match(r'^(else)(?:\s|{)', remainder) and + not re.match(r'^else\s+if\b', remainder)): + m =3D re.match(r'^(else)', remainder) + if m: + coff =3D off + len(m.group(1)) - 1 + coff_set =3D True + + if (ptype =3D=3D '' or ptype =3D=3D '(') and p =3D=3D '(': + level +=3D 1 + ptype =3D '(' + if ptype =3D=3D '(' and p =3D=3D ')': + level -=3D 1 + ptype =3D '(' if level !=3D 0 else '' + if level =3D=3D 0 and coff < soff: + coff =3D off + coff_set =3D True + + if (ptype =3D=3D '' or ptype =3D=3D '{') and p =3D=3D '{': + level +=3D 1 + ptype =3D '{' + if ptype =3D=3D '{' and p =3D=3D '}': + level -=3D 1 + ptype =3D '{' if level !=3D 0 else '' + if level =3D=3D 0: + if off + 1 < length and blk[off + 1] =3D=3D ';': + off +=3D 1 + break + + if ptype =3D=3D '#' and p =3D=3D '\n' and p_prev !=3D '\\': + level -=3D 1 + ptype =3D '' + off +=3D 1 + break + + off +=3D 1 + + if off =3D=3D length: + loff =3D length + 1 + line +=3D 1 + remain -=3D 1 + + statement =3D blk[soff:off + 1] if off + 1 <=3D len(blk) else blk[soff= :] + condition =3D blk[soff:coff + 1] if coff + 1 <=3D len(blk) else blk[so= ff:] + + return (statement, condition, line, max(remain + 1, 0), off - loff + 1= , level) + +def statement_lines(stmt): + stmt =3D re.sub(r'(?:^|\n).', '\n', stmt) + stmt =3D stmt.strip() + return stmt.count('\n') + 1 + +def statement_rawlines(stmt): + return stmt.count('\n') + 1 + +def statement_block_size(stmt): + stmt =3D re.sub(r'(?:^|\n).', '\n', stmt) + stmt =3D re.sub(r'^\s*\{', '', stmt) + stmt =3D re.sub(r'\}\s*$', '', stmt) + stmt =3D stmt.strip() + stmt_lines_count =3D stmt.count('\n') + 1 + stmt_stmts =3D stmt.count(';') + return max(stmt_lines_count, stmt_stmts) + +def pos_last_openparen(line): + opens =3D line.count('(') + closes =3D line.count(')') + if opens =3D=3D 0 or closes >=3D opens: + return -1 + last_open =3D 0 + for pos, c in enumerate(line): + if c =3D=3D '(': + last_open =3D pos + return len(expand_tabs(line[:last_open])) + 1 + +# ---- Value annotation (simplified from Perl) ---- + +av_preprocessor =3D False +av_pending =3D '_' +av_paren_type =3D ['E'] +av_pend_colon =3D 'O' + +def annotate_reset(): + global av_preprocessor, av_pending, av_paren_type, av_pend_colon + av_preprocessor =3D False + av_pending =3D '_' + av_paren_type =3D ['E'] + av_pend_colon =3D 'O' + +def annotate_values(stream, vtype): + global av_preprocessor, av_pending, av_paren_type, av_pend_colon + res =3D '' + var =3D '_' * len(stream) + var_list =3D list(var) + cur =3D stream + + while cur: + if not av_paren_type: + av_paren_type =3D ['E'] + + consumed =3D None + + m =3D re.match(r'^(\s+)', cur) + if m: + consumed =3D m.group(1) + if '\n' in consumed and av_preprocessor: + if av_paren_type: + vtype =3D av_paren_type.pop() + av_preprocessor =3D False + if consumed is None: + m =3D re.match(r'^(\(\s*' + Type + r'\s*)\)', cur) + if m and av_pending =3D=3D '_': + consumed =3D m.group(1) + av_paren_type.append(vtype) + vtype =3D 'c' + if consumed is None: + m =3D re.match(r'^(' + Type + r')\s*(?:' + Ident + r'|,|\)|\(|= \s*$)', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'T' + if consumed is None: + m =3D re.match(r'^(' + Modifier + r')\s*', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'T' + if consumed is None: + m =3D re.match(r'^(\#\s*define\s*' + Ident + r')(\(?)', cur) + if m: + consumed =3D m.group(1) + av_preprocessor =3D True + av_paren_type.append(vtype) + if m.group(2): + av_pending =3D 'N' + vtype =3D 'E' + if consumed is None: + m =3D re.match(r'^(\#\s*(?:undef\s*' + Ident + r'|include\b))'= , cur) + if m: + consumed =3D m.group(1) + av_preprocessor =3D True + av_paren_type.append(vtype) + if consumed is None: + m =3D re.match(r'^(\#\s*(?:ifdef|ifndef|if))', cur) + if m: + consumed =3D m.group(1) + av_preprocessor =3D True + av_paren_type.append(vtype) + av_paren_type.append(vtype) + vtype =3D 'E' + if consumed is None: + m =3D re.match(r'^(\#\s*(?:else|elif))', cur) + if m: + consumed =3D m.group(1) + av_preprocessor =3D True + if av_paren_type: + av_paren_type.append(av_paren_type[-1]) + vtype =3D 'E' + if consumed is None: + m =3D re.match(r'^(\#\s*endif)', cur) + if m: + consumed =3D m.group(1) + av_preprocessor =3D True + if av_paren_type: + av_paren_type.pop() + av_paren_type.append(vtype) + vtype =3D 'E' + if consumed is None: + m =3D re.match(r'^(\\\n)', cur) + if m: + consumed =3D m.group(1) + if consumed is None: + m =3D re.match(r'^(__attribute__)\s*\(?', cur) + if m: + consumed =3D m.group(1) + av_pending =3D vtype + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(sizeof)\s*(\()?', cur) + if m: + consumed =3D m.group(1) + if m.group(2): + av_pending =3D 'V' + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(if|while|for)\b', cur) + if m: + consumed =3D m.group(1) + av_pending =3D 'E' + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(case)', cur) + if m: + consumed =3D m.group(1) + av_pend_colon =3D 'C' + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(return|else|goto|typeof|__typeof__)\b', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(\()', cur) + if m: + consumed =3D m.group(1) + av_paren_type.append(av_pending) + av_pending =3D '_' + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(\))', cur) + if m: + consumed =3D m.group(1) + new_type =3D av_paren_type.pop() if av_paren_type else '_' + if new_type !=3D '_': + vtype =3D new_type + if consumed is None: + m =3D re.match(r'^(' + Ident + r')\s*\(', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'V' + av_pending =3D 'V' + if consumed is None: + m =3D re.match(r'^(' + Ident + r'\s*):(?:\s*\d+\s*(,|=3D|;))?'= , cur) + if m: + consumed =3D m.group(1) + if m.group(2) and vtype in ('C', 'T'): + av_pend_colon =3D 'B' + elif vtype =3D=3D 'E': + av_pend_colon =3D 'L' + vtype =3D 'V' + if consumed is None: + m =3D re.match(r'^(' + Ident + r'|' + Constant + r')', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'V' + if consumed is None: + m =3D re.match(r'^(' + Assignment + r')', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(;|\{|\})', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'E' + av_pend_colon =3D 'O' + if consumed is None: + m =3D re.match(r'^(,)', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'C' + if consumed is None: + m =3D re.match(r'^(\?)', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(:)', cur) + if m: + consumed =3D m.group(1) + idx =3D len(res) + if idx < len(var_list): + var_list[idx] =3D av_pend_colon + if av_pend_colon in ('C', 'L'): + vtype =3D 'E' + else: + vtype =3D 'N' + av_pend_colon =3D 'O' + if consumed is None: + m =3D re.match(r'^(\[)', cur) + if m: + consumed =3D m.group(1) + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(-(?![->])|\+(?!\+)|\*|&&|&)', cur) + if m: + consumed =3D m.group(1) + variant =3D 'B' if vtype =3D=3D 'V' else 'U' + idx =3D len(res) + if idx < len(var_list): + var_list[idx] =3D variant + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(' + Operators + r')', cur) + if m: + consumed =3D m.group(1) + if consumed not in ('++', '--'): + vtype =3D 'N' + if consumed is None: + m =3D re.match(r'^(.)', cur) + if m: + consumed =3D m.group(1) + + if consumed: + cur =3D cur[len(consumed):] + res +=3D vtype * len(consumed) + + return (res, ''.join(var_list)) + +# ---- Spelling ---- + +misspellings =3D None +spelling_fix =3D {} + +def load_spelling(): + global misspellings, spelling_fix + if os.path.isfile(spelling_file): + try: + with open(spelling_file, 'r', encoding=3D'utf-8', errors=3D're= place') as f: + for line in f: + line =3D line.strip() + if not line or line.startswith('#'): + continue + parts =3D line.split('||', 1) + if len(parts) =3D=3D 2: + spelling_fix[parts[0]] =3D parts[1] + except IOError: + print(f"No typos will be found - file '{spelling_file}': not r= eadable", file=3Dsys.stderr) + + if codespell and os.path.isfile(codespellfile): + try: + with open(codespellfile, 'r', encoding=3D'utf-8', errors=3D're= place') as f: + for line in f: + line =3D line.strip() + if not line or line.startswith('#') or ', disabled' in= line.lower(): + continue + line =3D line.split(',')[0] + parts =3D line.split('->', 1) + if len(parts) =3D=3D 2: + spelling_fix[parts[0]] =3D parts[1] + except IOError: + print(f"No codespell typos will be found - file '{codespellfil= e}': not readable", file=3Dsys.stderr) + + if spelling_fix: + misspellings =3D '|'.join(sorted(spelling_fix.keys())) + +# ---- Const structs ---- + +const_structs =3D None + +def load_const_structs(): + global const_structs + if os.path.isfile(conststructsfile): + words =3D [] + try: + with open(conststructsfile, 'r', encoding=3D'utf-8', errors=3D= 'replace') as f: + for line in f: + line =3D line.strip() + if not line or line.startswith('#') or ' ' in line: + continue + words.append(line) + except IOError: + pass + if words: + const_structs =3D '|'.join(words) + +# ---- Git helpers ---- + +def git_is_single_file(filename): + if not which("git") or not os.path.exists(gitroot): + return False + try: + output =3D subprocess.run(f'{git_command} ls-files -- {filename}', + shell=3DTrue, capture_output=3DTrue, text= =3DTrue).stdout + count =3D output.count('\n') + return count =3D=3D 1 and output.strip() =3D=3D filename + except Exception: + return False + +def git_commit_info(commit, cid, desc): + if not which("git") or not os.path.exists(gitroot): + return (cid, desc) + try: + output =3D subprocess.run( + f"{git_command} log --no-color --format=3D'%H %s' -1 {commit}", + shell=3DTrue, capture_output=3DTrue, text=3DTrue, stderr=3Dsub= process.STDOUT).stdout.strip() + lines =3D output.split('\n') + except Exception: + return (cid, desc) + + if not lines: + return (cid, desc) + + if 'error: short SHA1' in lines[0] and 'is ambiguous' in lines[0]: + pass + elif 'fatal: ambiguous argument' in lines[0] or 'fatal: bad object' in= lines[0]: + cid =3D None + else: + cid =3D lines[0][:12] + desc =3D lines[0][41:] if len(lines[0]) > 41 else '' + + return (cid, desc) + +# ---- Maintained/obsolete check ---- + +maintained_status =3D {} + +def is_maintained_obsolete(filename): + global maintained_status + if not tree or not root: + return False + gm_script =3D os.path.join(root, 'scripts', 'get_maintainer.pl') + if not os.path.exists(gm_script): + gm_script =3D os.path.join(root, 'scripts', 'get_maintainer.py') + if not os.path.exists(gm_script): + return False + + if filename not in maintained_status: + try: + if gm_script.endswith('.py'): + cmd =3D f'python3 {gm_script} --status --nom --nol --nogit= --nogit-fallback -f {filename}' + else: + cmd =3D f'perl {gm_script} --status --nom --nol --nogit --= nogit-fallback -f {filename}' + maintained_status[filename] =3D subprocess.run( + cmd, shell=3DTrue, capture_output=3DTrue, text=3DTrue).std= out + except Exception: + return False + + return bool(re.search(r'obsolete', maintained_status.get(filename, '')= , re.IGNORECASE)) + +def is_SPDX_License_valid(license_str): + if not tree or not which("python3") or not root: + return True + spdxcheck =3D os.path.join(root, 'scripts', 'spdxcheck.py') + if not os.path.isfile(spdxcheck) or not os.path.exists(gitroot): + return True + try: + result =3D subprocess.run( + f'cd "{os.path.abspath(root)}"; echo "{license_str}" | scripts= /spdxcheck.py -', + shell=3DTrue, capture_output=3DTrue, text=3DTrue) + return result.stdout =3D=3D "" + except Exception: + return True + +# ---- Verbose docs loading ---- + +def load_docs(): + global verbose_messages + if not os.path.isfile(docsfile): + return + try: + with open(docsfile, 'r', encoding=3D'utf-8', errors=3D'replace') a= s f: + msg_type =3D '' + desc =3D '' + in_desc =3D False + for raw_line_text in f: + line =3D raw_line_text.rstrip() + m =3D re.match(r'^\s*\*\*(.+)\*\*$', line) + if m: + if desc: + verbose_messages[msg_type] =3D desc.strip() + msg_type =3D m.group(1) + desc =3D '' + in_desc =3D True + elif in_desc: + if re.match(r'^(?:\s{4,}|$)', line): + desc +=3D re.sub(r'^\s{4}', '', line) + '\n' + else: + if desc: + verbose_messages[msg_type] =3D desc.strip() + msg_type =3D '' + desc =3D '' + in_desc =3D False + if desc: + verbose_messages[msg_type] =3D desc.strip() + except IOError: + pass + +# ---- List types ---- + +def do_list_types(): + """List all message types by scanning our own source.""" + try: + with open(os.path.abspath(sys.argv[0]), 'r', encoding=3D'utf-8') a= s f: + text =3D f.read() + except IOError: + return + + types =3D {} + for m in re.finditer(r'(?:(\bCHK|\bWARN|\bERROR)\s*\()\s*["\']([^"\']+= )["\']', text): + level =3D m.group(1) + msg_type =3D m.group(2) + if msg_type in types: + if types[msg_type] !=3D level: + types[msg_type] +=3D ',' + level + else: + types[msg_type] =3D level + + print("#\tMessage type\n") + if color_enabled: + print(f" ( Color coding: {RED}ERROR{RESET} | {YELLOW}WARNING{RESET= } | {GREEN}CHECK{RESET} | Multiple levels / Undetermined )\n") + + count =3D 0 + for msg_type in sorted(types.keys()): + count +=3D 1 + display =3D msg_type + if color_enabled: + level =3D types[msg_type] + if level =3D=3D 'ERROR': + display =3D RED + msg_type + RESET + elif level =3D=3D 'WARN': + display =3D YELLOW + msg_type + RESET + elif level =3D=3D 'CHK': + display =3D GREEN + msg_type + RESET + print(f"{count}\t{display}") + if verbose and msg_type in verbose_messages: + msg =3D verbose_messages[msg_type].replace('\n', '\n\t') + print(f"\t{msg}\n") + +# ---- Main process function ---- + +def process(filename): + global rawlines, lines, fixed, fixed_inserted, fixed_deleted, fixlinenr + global report_list, cnt_lines, cnt_error, cnt_warn, cnt_chk, clean + global prefix, rpt_cleaners, check, modifierListFile, typeListFile + + linenr =3D 0 + prevline =3D "" + prevrawline =3D "" + stashline =3D "" + stashrawline =3D "" + length =3D 0 + indent =3D 0 + previndent =3D 0 + stashindent =3D 0 + + clean =3D 1 + signoff =3D 0 + fixes_tag =3D 0 + is_revert =3D False + needs_fixes_tag =3D "" + author =3D '' + authorsignoff =3D 0 + author_sob =3D '' + is_patch =3D False + in_header_lines =3D 0 if file_mode else 1 + in_commit_log =3D False + has_patch_separator =3D False + has_commit_log =3D False + commit_log_lines =3D 0 + commit_log_possible_stack_dump =3D False + commit_log_long_line =3D False + commit_log_has_diff =3D False + reported_maintainer_file =3D False + non_utf8_charset =3D False + last_git_commit_id_linenr =3D -1 + last_blank_line =3D 0 + last_coalesced_string_linenr =3D -1 + + report_list =3D [] + cnt_lines =3D 0 + cnt_error =3D 0 + cnt_warn =3D 0 + cnt_chk =3D 0 + + realfile =3D '' + realline =3D 0 + realcnt =3D 0 + here =3D '' + context_function =3D None + in_comment =3D False + first_line =3D 0 + p1_prefix =3D '' + prev_values =3D 'E' + + suppress_ifbraces =3D {} + suppress_whiletrailers =3D {} + suppress_export =3D {} + suppress_statement =3D 0 + + signatures =3D {} + setup_docs_list =3D [] + setup_docs =3D False + camelcase_file_seeded =3D False + checklicenseline =3D 1 + + emitted_corrupt =3D 0 + + # Pre-scan: sanitize lines and build lines[] + sanitise_line_reset() + lines_local =3D [] + local_fixed =3D [] + + for raw_idx, rawline_text in enumerate(rawlines): + line_text =3D rawline_text + + if fix: + local_fixed.append(rawline_text) + + if re.match(r'^\+\+\+\s+(\S+)', rawline_text): + setup_docs =3D False + m =3D re.match(r'^\+\+\+\s+(\S+)', rawline_text) + if m and re.search(r'Documentation/admin-guide/kernel-paramete= rs\.txt$', m.group(1)): + setup_docs =3D True + + m =3D re.match(r'^@@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? @@', rawline_t= ext) + if m: + realline =3D int(m.group(1)) - 1 + realcnt =3D int(m.group(3)) + 1 if m.group(2) else 2 + in_comment =3D False + + # Guess if we're in a comment + edge =3D None + cnt =3D realcnt + for ln in range(raw_idx + 1, len(rawlines)): + if rawlines[ln].startswith('-'): + continue + cnt -=3D 1 + if cnt <=3D 0: + break + m2 =3D re.search(r'(/\*|\*/)', rawlines[ln]) + if m2 and not re.search(r'"[^"]*(?:/\*|\*/)[^"]*"', rawlin= es[ln]): + edge =3D m2.group(1) + break + + if edge =3D=3D '*/': + in_comment =3D True + + if edge is None and raw_idx + 1 < len(rawlines): + if re.match(r'^.\s*(?:\*\*+| \*)(?:\s|$)', rawlines[raw_id= x + 1] if raw_idx + 1 < len(rawlines) else ''): + in_comment =3D True + + sanitise_line_reset(in_comment) + elif realcnt and re.match(r'^(?:\+| |$)', rawline_text): + line_text =3D sanitise_line(rawline_text) + + lines_local.append(line_text) + + if realcnt > 1: + if re.match(r'^(?:\+| |$)', line_text): + realcnt -=3D 1 + else: + realcnt =3D 0 + + if setup_docs and line_text.startswith('+'): + setup_docs_list.append(line_text) + + lines =3D lines_local + if fix: + fixed[:] =3D local_fixed + + # Main processing loop + prefix_local =3D '' + realcnt =3D 0 + linenr =3D 0 + fixlinenr =3D -1 + hunk_line =3D False + + for line_idx, line in enumerate(lines): + linenr =3D line_idx + 1 + fixlinenr +=3D 1 + + sline =3D line.replace(COMMENT_CHAR, ' ') if line else '' + rawline =3D rawlines[line_idx] if line_idx < len(rawlines) else '' + + # Check mode change / rename / patch start + if not in_commit_log: + if (re.match(r'^ mode change [0-7]+ =3D> [0-7]+ \S+\s*$', line= or '') or + re.match(r'^rename (?:from|to) \S+\s*$', line or '') or + re.match(r'^diff --git a/[\w/._\-]+ b/\S+\s*$', line or ''= )): + is_patch =3D True + + # Extract line range + if not in_commit_log: + m =3D re.match(r'^@@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? @@(.*)', l= ine or '') + if m: + context_text =3D m.group(4) + is_patch =3D True + first_line =3D linenr + 1 + realline =3D int(m.group(1)) - 1 + realcnt =3D int(m.group(3)) + 1 if m.group(2) else 2 + annotate_reset() + prev_values =3D 'E' + suppress_ifbraces =3D {} + suppress_whiletrailers =3D {} + suppress_export =3D {} + suppress_statement =3D 0 + m2 =3D re.search(r'\b(\w+)\s*\(', context_text or '') + context_function =3D m2.group(1) if m2 else None + continue + + # Track lines in the hunk + if re.match(r'^( |\+|$)', line or ''): + realline +=3D 1 + if realcnt: + realcnt -=3D 1 + length, indent =3D line_stats(rawline) + prevline, stashline =3D stashline, line + previndent, stashindent =3D stashindent, indent + prevrawline, stashrawline =3D stashrawline, rawline + elif realcnt =3D=3D 1: + realcnt =3D 0 + + hunk_line =3D (realcnt !=3D 0) + + if not file_mode: + here =3D f"#{linenr}: " + else: + here =3D f"#{realline}: " + + # Extract filename + found_file =3D False + m =3D re.match(r'^diff --git.*?(\S+)$', line or '') + if m: + realfile =3D m.group(1) + if not file_mode: + realfile =3D re.sub(r'^[^/]*/', '', realfile) + in_commit_log =3D False + found_file =3D True + else: + m =3D re.match(r'^\+\+\+\s+(\S+)', line or '') + if m: + realfile =3D m.group(1) + if not file_mode: + realfile =3D re.sub(r'^[^/]*/', '', realfile) + in_commit_log =3D False + p1_prefix =3D re.match(r'^([^/]*/)', m.group(1)) + p1_prefix =3D p1_prefix.group(1) if p1_prefix else '' + + if re.match(r'^include/asm/', realfile): + ERROR("MODIFIED_INCLUDE_ASM", + "do not modify files in include/asm, change arch= itecture specific files in arch//include/asm\n" + f"{here}{ra= wline}\n") + found_file =3D True + + # Set prefix for error reporting + if showfile: + prefix =3D f"{realfile}:{realline}: " + elif emacs: + prefix =3D f"{filename}:{realline if file_mode else linenr}: " + else: + prefix =3D '' + + if found_file: + if is_maintained_obsolete(realfile): + WARN("OBSOLETE", + f"{realfile} is marked as 'obsolete' in the MAINTAINE= RS hierarchy. No unnecessary modifications please.\n") + if re.match(r'^(?:drivers/net/|net/|drivers/staging/)', realfi= le): + check =3D True + else: + check =3D check_orig + checklicenseline =3D 1 + continue + + here +=3D f"FILE: {realfile}:{realline}:" if realcnt else '' + + hereline =3D f"{here}\n{rawline}\n" + herecurr =3D f"{here}\n{rawline}\n" + hereprev =3D f"{here}\n{prevrawline}\n{rawline}\n" + + if realcnt: + cnt_lines +=3D 1 + + # ---- Commit log checks ---- + if in_commit_log: + if line and not re.match(r'^\s*$', line): + commit_log_lines +=3D 1 + elif has_commit_log and commit_log_lines < 2: + WARN("COMMIT_MESSAGE", + "Missing commit description - Add an appropriate one\n") + commit_log_lines =3D 2 + + # Check for diff in commit message + if (in_commit_log and not commit_log_has_diff and line and + (re.match(r'^\s+diff\b.*a/([\w/]+)', line) or + re.match(r'^\s*(?:---\s+a/|\+\+\+\s+b/)', line) or + re.match(r'^\s*@@ -\d+,\d+ \+\d+,\d+ @@', line))): + ERROR("DIFF_IN_COMMIT_MSG", + "Avoid using diff content in the commit message - patch(= 1) might not work\n" + herecurr) + commit_log_has_diff =3D True + + # Incorrect file permissions + if line and re.match(r'^new (file )?mode.*[7531]\d{0,2}$', line): + permhere =3D here + f"FILE: {realfile}\n" + if not re.match(r'^scripts/', realfile) and not re.search(r'\.= (py|pl|awk|sh)$', realfile): + ERROR("EXECUTE_PERMISSIONS", + "do not set execute permissions for source files\n" = + permhere) + + # Check for From: + if line and re.match(r'^From:\s*(.*)', line, re.IGNORECASE): + author =3D re.match(r'^From:\s*(.*)', line, re.IGNORECASE).gro= up(1) + + # Check for signoff + if line and re.match(r'^\s*signed-off-by:\s*(.*)', line, re.IGNORE= CASE): + signoff +=3D 1 + in_commit_log =3D False + + # Check for patch separator + if line =3D=3D '---': + has_patch_separator =3D True + in_commit_log =3D False + + # MAINTAINERS update check + if line and re.match(r'^\s*MAINTAINERS\s*\|', line): + reported_maintainer_file =3D True + + # Check if it's the start of commit log + if in_header_lines and realfile =3D=3D '': + if rawline and not (re.match(r'^\s+(?:\S|$)', rawline) or + re.match(r'^(?:commit\b|from\b|[\w-]+:)', = rawline, re.IGNORECASE)): + in_header_lines =3D 0 + in_commit_log =3D True + has_commit_log =3D True + + # Check for Fixes: tag + if (not in_header_lines and line and + re.match(r'^\s*(fixes:?)\s*(?:commit\s*)?([0-9a-f]{5,40})', li= ne, re.IGNORECASE)): + fixes_tag =3D 1 + m =3D re.match(r'^\s*(fixes:?)\s*(?:commit\s*)?([0-9a-f]{5,40}= )', line, re.IGNORECASE) + tag =3D m.group(1) + orig_commit =3D m.group(2) + # Simplified Fixes: tag check + if tag !=3D "Fixes:": + cid, ctitle =3D git_commit_info(orig_commit, '0123456789ab= ', 'commit title') + if cid is not None: + fixed_str =3D f'Fixes: {cid} ("{ctitle}")' + WARN("BAD_FIXES_TAG", + f"Please use correct Fixes: style 'Fixes: <12+ ch= ars of sha1> (\"\")' - ie: '{fixed_str}'\n" + herecurr) + + # Check for Gerrit Change-Id + if realfile =3D=3D '' and not has_patch_separator and line and re.= match(r'^\s*change-id:', line, re.IGNORECASE): + ERROR("GERRIT_CHANGE_ID", + "Remove Gerrit Change-Id's before submitting upstream\n"= + herecurr) + + # Check commit log line length + if (in_commit_log and not commit_log_long_line and line and + len(line) > 75 and + not re.match(r'^\s*[a-zA-Z0-9_/\.]+\s+\|\s+\d+', line) and + not re.match(r'^\s*(?:[\w.\-+]*/)+[\w.\-+]+:', line) and + not re.match(r'^\s*(?:Fixes:|https?:|' + link_tags_search + '|= ' + signature_tags + r')', line, re.IGNORECASE) and + not commit_log_possible_stack_dump): + WARN("COMMIT_LOG_LONG_LINE", + f"Prefer a maximum 75 chars per line (possible unwrapped = commit description?)\n" + herecurr) + commit_log_long_line =3D True + + # Check for This reverts commit + if not in_header_lines and not is_patch and line and re.match(r'^T= his reverts commit', line): + is_revert =3D True + + # Bug/crash indicators + if (not in_header_lines and not is_patch and line and + re.search(r'((?:(?:BUG: K\.|UB)SAN: |Call Trace:|stable@|syzka= ller))', line)): + needs_fixes_tag =3D re.search(r'((?:(?:BUG: K\.|UB)SAN: |Call = Trace:|stable@|syzkaller))', line).group(1) + + # Check for lines starting with # + if in_commit_log and line and line.startswith('#'): + WARN("COMMIT_COMMENT_SYMBOL", + "Commit log lines starting with '#' are dropped by git as= comments\n" + herecurr) + + # ignore non-hunk lines and lines being removed + if not hunk_line or (line and line.startswith('-')): + continue + + # ---- Whitespace checks ---- + + # DOS line endings + if line and line.startswith('+') and '\r' in rawline: + herevet =3D f"{here}\n{cat_vet(rawline)}\n" + ERROR("DOS_LINE_ENDINGS", "DOS line endings\n" + herevet) + # Trailing whitespace + elif rawline and (re.match(r'^\+.*\S\s+$', rawline) or re.match(r'= ^\+\s+$', rawline)): + herevet =3D f"{here}\n{cat_vet(rawline)}\n" + ERROR("TRAILING_WHITESPACE", "trailing whitespace\n" + herevet) + rpt_cleaners =3D 1 + + # Check we are in a valid source file + if not re.search(r'\.(h|c|rs|s|S|sh|dtsi|dts)$', realfile): + continue + + # SPDX check + if realline =3D=3D checklicenseline: + if rawline and rawline.startswith('+'): + if re.search(r'SPDX-License-Identifier:', rawline): + m =3D re.search(r'(SPDX-License-Identifier: .*)', rawl= ine) + if m: + spdx_license =3D m.group(1) + if not is_SPDX_License_valid(spdx_license): + WARN("SPDX_LICENSE_TAG", + f"'{spdx_license}' is not supported in LI= CENSES/...\n" + herecurr) + + # Line length check + if line and line.startswith('+') and length > max_line_length: + msg_type =3D "LONG_LINE" + + # Skip URLs + if re.search(r'\b[a-z][\w.+\-]*://\S+', rawline, re.IGNORECASE= ): + msg_type =3D "" + # Skip strings + elif re.match(r'^\+\s*' + String + r'\s*(?:\s*|,|\)\s*;)\s*$',= line): + msg_type =3D "" + elif re.match(r'^\+\s*#\s*define\s+\w+\s+' + String + r'$', li= ne): + msg_type =3D "" + + if msg_type and show_type("LONG_LINE") and show_type(msg_type): + msg_level =3D CHK if file_mode else WARN + msg_level(msg_type, + f"line length of {length} exceeds {max_line_lengt= h} columns\n" + herecurr) + + # Check we are in a valid C source file + if not re.search(r'\.(h|c|pl|dtsi|dts)$', realfile): + continue + + # Code indent (tabs vs spaces) + # At the beginning of a line any tabs must come first and anything + # more than tabsize spaces must use tabs + if rawline and (re.match(r'^\+\s* \t\s*\S', rawline) or + re.match(r'^\+ {' + str(tabsize) + r',}\s*', rawli= ne)): + herevet =3D f"{here}\n{cat_vet(rawline)}\n" + ERROR("CODE_INDENT", "code indent should use tabs where possib= le\n" + herevet) + rpt_cleaners =3D 1 + + # Space before tab + if rawline and rawline.startswith('+') and ' \t' in rawline: + herevet =3D f"{here}\n{cat_vet(rawline)}\n" + WARN("SPACE_BEFORE_TAB", "please, no space before tabs\n" + he= revet) + + # Check we are in a valid C source file + if not re.search(r'\.(h|c)$', realfile): + continue + + # Function start detection + if (sline and re.match(r'^\+\{\s*$', sline) and + prevline and re.match(r'^\+(?:(?:(?:' + Storage + r'|' + Inlin= e + r')\s*)*\s*' + Type + r'\s*)?(' + Ident + r')\(', prevline)): + m =3D re.match(r'^\+(?:(?:(?:' + Storage + r'|' + Inline + r')= \s*)*\s*' + Type + r'\s*)?(' + Ident + r')\(', prevline) + if m: + context_function =3D m.group(1) + + # Function end + if sline and re.match(r'^\+\}\s*$', sline): + context_function =3D None + + # ---- Simple pattern-based checks ---- + + # printk should use KERN_* levels + if line and re.search(r'\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)', line): + WARN("PRINTK_WITHOUT_KERN_LEVEL", + "printk() should include KERN_<LEVEL> facility level\n" += herecurr) + + # ENOSYS + if line and re.search(r'\bENOSYS\b', line): + WARN("ENOSYS", + "ENOSYS means 'invalid syscall nr' and nothing else\n" + = herecurr) + + # ENOTSUPP + if not file_mode and line and re.search(r'\bENOTSUPP\b', line): + WARN("ENOTSUPP", + "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP\n"= + herecurr) + + # BUG + if line and re.search(r'\b(?!AA_|BUILD_|IDA_|KVM_|RWLOCK_|snd_|SPI= N_)(?:[a-zA-Z_]*_)?BUG(?:_ON)?(?:_[A-Z_]+)?\s*\(', line): + msg_level =3D CHK if file_mode else WARN + msg_level("AVOID_BUG", + "Do not crash the kernel unless it is absolutely una= voidable--use WARN_ON_ONCE() plus recovery code (if feasible) instead of BU= G() or variants\n" + herecurr) + + # LINUX_VERSION_CODE + if line and re.search(r'\bLINUX_VERSION_CODE\b', line): + WARN("LINUX_VERSION_CODE", + "LINUX_VERSION_CODE should be avoided, code should be for= the version to which it is merged\n" + herecurr) + + # volatile + if line and re.search(r'\bvolatile\b', line) and not re.search(r'\= b(?:__asm__|asm)\s+(?:__volatile__|volatile)\b', line): + WARN("VOLATILE", + "Use of volatile is usually wrong: see Documentation/proc= ess/volatile-considered-harmful.rst\n" + herecurr) + + # trace_printk + if line: + m =3D re.search(r'\b(trace_printk|trace_puts|ftrace_vprintk)\s= *\(', line) + if m: + WARN("TRACE_PRINTK", + f"Do not use {m.group(1)}() in production code (this = can be ignored if built only with a debug config option)\n" + herecurr) + + # printk_ratelimit + if line and re.search(r'\bprintk_ratelimit\s*\(', line): + WARN("PRINTK_RATELIMITED", + "Prefer printk_ratelimited or pr_<level>_ratelimited to p= rintk_ratelimit\n" + herecurr) + + # udelay + if line: + m =3D re.search(r'\budelay\s*\(\s*(\d+)\s*\)', line) + if m: + delay =3D int(m.group(1)) + if delay >=3D 10: + CHK("USLEEP_RANGE", + "usleep_range is preferred over udelay; see functi= on description of usleep_range() and udelay().\n" + herecurr) + if delay > 2000: + WARN("LONG_UDELAY", + "long udelay - prefer mdelay; see function descri= ption of mdelay().\n" + herecurr) + + # msleep + if line: + m =3D re.search(r'\bmsleep\s*\((\d+)\);', line) + if m and int(m.group(1)) < 20: + WARN("MSLEEP", + "msleep < 20ms can sleep for up to 20ms; see function= description of msleep().\n" + herecurr) + + # jiffies comparison + if line and re.search(r'\bjiffies\s*' + Compare + r'|' + Compare += r'\s*jiffies\b', line): + WARN("JIFFIES_COMPARISON", + "Comparing jiffies is almost always wrong; prefer time_af= ter, time_before and friends\n" + herecurr) + + # strcpy/strlcpy/strncpy + if line and re.search(r'\bstrcpy\s*\(', line) and not is_userspace= (realfile): + WARN("STRCPY", + "Prefer strscpy over strcpy - see: https://github.com/KSP= P/linux/issues/88\n" + herecurr) + if line and re.search(r'\bstrlcpy\s*\(', line) and not is_userspac= e(realfile): + WARN("STRLCPY", + "Prefer strscpy over strlcpy - see: https://github.com/KS= PP/linux/issues/89\n" + herecurr) + if line and re.search(r'\bstrncpy\s*\(', line) and not is_userspac= e(realfile): + WARN("STRNCPY", + "Prefer strscpy, strscpy_pad, or __nonstring over strncpy= - see: https://github.com/KSPP/linux/issues/90\n" + herecurr) + + # yield() + if line and re.search(r'\byield\s*\(\s*\)', line): + WARN("YIELD", + "Using yield() is generally wrong. See yield() kernel-doc= (sched/core.c)\n" + herecurr) + + # __FUNCTION__ + if line and re.search(r'\b__FUNCTION__\b', line): + WARN("USE_FUNC", + "__func__ should be used instead of gcc specific __FUNCTI= ON__\n" + herecurr) + + # __DATE__, __TIME__, __TIMESTAMP__ + if line: + for m in re.finditer(r'\b(__(?:DATE|TIME|TIMESTAMP)__)\b', lin= e): + ERROR("DATE_TIME", + f"Use of the '{m.group(1)}' macro makes the build no= n-deterministic\n" + herecurr) + + # #if 0 + if line and re.match(r'^.\s*\#\s*if\s+0\b', line): + WARN("IF_0", + "Consider removing the code enclosed by this #if 0 and it= s #endif\n" + herecurr) + + # #if 1 + if line and re.match(r'^.\s*\#\s*if\s+1\b', line): + WARN("IF_1", + "Consider removing the #if 1 and its #endif\n" + herecurr) + + # sizeof without parenthesis + if line: + m =3D re.search(r'\bsizeof\s+((?:\*\s*|)' + Lval + r'|' + Type= + r'(?:\s+' + Lval + r'|))', line) + if m: + WARN("SIZEOF_PARENTHESIS", + f"sizeof {m.group(1)} should be sizeof({m.group(1).st= rip()})\n" + herecurr) + + # sizeof(&) + if line and re.search(r'\bsizeof\s*\(\s*&', line): + WARN("SIZEOF_ADDRESS", "sizeof(& should be avoided\n" + herecu= rr) + + # spinlock_t + if line and re.match(r'^.\s*\bstruct\s+spinlock\s+\w+\s*;', line): + WARN("USE_SPINLOCK_T", + "struct spinlock should be spinlock_t\n" + herecurr) + + # new typedefs + if (line and re.search(r'\btypedef\s', line) and + not re.search(r'\btypedef\s+' + Type + r'\s*\(\s*\*?' + Ident = + r'\s*\)\s*\(', line) and + not re.search(r'\btypedef\s+' + Type + r'\s+' + Ident + r'\s*\= (', line) and + not re.search(r'\b' + typeTypedefs + r'\b', line) and + not re.search(r'\b__bitwise\b', line)): + WARN("NEW_TYPEDEFS", "do not add new typedefs\n" + herecurr) + + # in_atomic + if line and re.search(r'\bin_atomic\s*\(', line): + if re.match(r'^drivers/', realfile): + ERROR("IN_ATOMIC", "do not use in_atomic in drivers\n" + h= erecurr) + elif not re.match(r'^kernel/', realfile): + WARN("IN_ATOMIC", + "use of in_atomic() is incorrect outside core kernel = code\n" + herecurr) + + # NR_CPUS + if (line and re.search(r'\bNR_CPUS\b', line) and + not re.search(r'^\s*#\s*if\b.*\bNR_CPUS\b', line) and + not re.search(r'^\s*#\s*define\b.*\bNR_CPUS\b', line) and + not re.search(r'\bNR_CPUS[^\]]*\]', line)): + WARN("NR_CPUS", + "usage of NR_CPUS is often wrong - consider using cpu_pos= sible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" + herecurr) + + # deprecated APIs + if line: + m =3D re.search(r'\b(' + deprecated_apis_search + r')\b\s*\(',= line) + if m: + api =3D m.group(1) + new_api =3D deprecated_apis.get(api, '') + WARN("DEPRECATED_API", + f"Deprecated use of '{api}', prefer '{new_api}' inste= ad\n" + herecurr) + + # const_structs + if (const_structs and line and + not re.search(r'\bconst\b', line) and + re.search(r'\bstruct\s+(' + const_structs + r')\b(?!\s*\{)', l= ine)): + m =3D re.search(r'\bstruct\s+(' + const_structs + r')\b', line) + WARN("CONST_STRUCT", + f"struct {m.group(1)} should normally be const\n" + herec= urr) + + # multiple semicolons + if line and re.search(r';\s*;\s*$', line): + WARN("ONE_SEMICOLON", + "Statements terminations use 1 semicolon\n" + herecurr) + + # spaces between function name and ( + if line: + for m in re.finditer(r'(' + Ident + r')\s+\(', line): + name =3D m.group(1) + if name in ('if', 'for', 'while', 'switch', 'return', 'cas= e', + 'volatile', '__volatile__', '__attribute__', '= format', + '__extension__', 'asm', '__asm__', 'scoped_gua= rd'): + continue + ctx_before =3D line[:m.start()] + if re.match(r'^.\s*#\s*define\s*$', ctx_before): + continue + if re.match(r'^.\s*#\s*elif\s*$', ctx_before + name): + continue + WARN("SPACING", + f"space prohibited between function name and open par= enthesis '('\n" + herecurr) + + # space before open brace + if line and (re.search(r'\(.*\)\{', line) and not re.search(r'\(' = + Type + r'\)\{', line)): + ERROR("SPACING", + "space required before the open brace '{'\n" + herecurr) + if line and re.search(r'\b(?:else|do)\{', line): + ERROR("SPACING", + "space required before the open brace '{'\n" + herecurr) + + # space after close brace + if line and re.search(r'\}(?!(?:,|;|\)|\}))\S', line): + ERROR("SPACING", + "space required after that close brace '}'\n" + herecurr) + + # Need space before ( after if/while etc + if line and re.search(r'\b(if|while|for|switch)\(', line): + ERROR("SPACING", + "space required before the open parenthesis '('\n" + her= ecurr) + + # return errno should be negative + if sline: + m =3D re.search(r'\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\= s*|\s*)[;:,]', sline) + if m: + name =3D m.group(1) + if name not in ('EOF', 'ERROR') and not name.startswith('E= POLL'): + WARN("USE_NEGATIVE_ERRNO", + f"return of an errno should typically be negative= (ie: return -{name})\n" + herecurr) + + # Malformed #include + if rawline: + m =3D re.match(r'^.\s*#\s*include\s+[<"](.*)[">]', rawline) + if m: + if '//' in m.group(1): + ERROR("MALFORMED_INCLUDE", + "malformed #include filename\n" + herecurr) + + # CamelCase (simplified) + if line and line.startswith('+'): + for m in re.finditer(r'\b(' + Ident + r')\b', line): + word =3D m.group(1) + if (re.search(r'[A-Z][a-z]|[a-z][A-Z]', word) and + word !=3D '_Generic' and + not re.match(r'^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]', word= ) and + not re.match(r'^(?:Clear|Set|TestClear|TestSet|)Page[A= -Z]', word) and + not re.match(r'^ETHTOOL_LINK_MODE_', word)): + if word not in camelcase: + camelcase[word] =3D True + CHK("CAMELCASE", f"Avoid CamelCase: <{word}>\n" + = herecurr) + + # Embedded filename + if rawline and realfile and re.search(r'^\+.*\b' + re.escape(realf= ile) + r'\b', rawline): + WARN("EMBEDDED_FILENAME", + "It's generally not useful to have the filename in the fi= le\n" + herecurr) + + # FSF mailing address + if rawline and (re.search(r'\bwrite to the Free', rawline, re.IGNO= RECASE) or + re.search(r'\b675\s+Mass\s+Ave', rawline, re.IGNOR= ECASE) or + re.search(r'\b59\s+Temple\s+Pl', rawline, re.IGNOR= ECASE) or + re.search(r'\b51\s+Franklin\s+St', rawline, re.IGN= ORECASE)): + msg_level =3D CHK if file_mode else ERROR + msg_level("FSF_MAILING_ADDRESS", + "Do not include the paragraph about writing to the F= ree Software Foundation's mailing address from the sample GPL notice. The F= SF has changed addresses in the past, and may do so again. Linux already in= cludes a copy of the GPL.\n" + f"{here}\n{cat_vet(rawline)}\n") + + # Spelling check + if (misspellings and line and + (in_commit_log or line.startswith('+') or re.match(r'^Subject:= ', line, re.IGNORECASE))): + for m in re.finditer(r'(?:^|[^\w\-\'`])(' + misspellings + r')= (?:[^\w\-\'`]|$)', rawline, re.IGNORECASE): + typo =3D m.group(1) + typo_fix_val =3D spelling_fix.get(typo.lower(), '') + if typo_fix_val: + if typo[0].isupper(): + typo_fix_val =3D typo_fix_val[0].upper() + typo_fi= x_val[1:] + if typo.isupper(): + typo_fix_val =3D typo_fix_val.upper() + msg_level =3D CHK if file_mode else WARN + msg_level("TYPO_SPELLING", + f"'{typo}' may be misspelled - perhaps '{typ= o_fix_val}'?\n" + herecurr) + + # ---- End of file checks ---- + + if not rawlines: + return 0 + + if mailback and (clean =3D=3D 1 or not is_patch): + return 0 + + if not chk_patch and not is_patch: + return 0 + + if not is_patch and not re.search(r'cover-letter\.patch$', filename): + ERROR("NOT_UNIFIED_DIFF", + "Does not appear to be a unified-diff format patch\n") + + if is_patch and has_commit_log and chk_fixes_tag: + if needs_fixes_tag and not is_revert and not fixes_tag: + WARN("MISSING_FIXES_TAG", + f"The commit message has '{needs_fixes_tag}', perhaps it = also needs a 'Fixes:' tag?\n") + + if is_patch and has_commit_log and chk_signoff: + if signoff =3D=3D 0: + ERROR("MISSING_SIGN_OFF", + "Missing Signed-off-by: line(s)\n") + + # Print report + output =3D ''.join(report_list) + if output: + sys.stdout.write(output) + + if summary and not (clean =3D=3D 1 and quiet >=3D 1): + if summary_file: + sys.stdout.write(f"{filename} ") + chk_str =3D f"{cnt_chk} checks, " if check else "" + print(f"total: {cnt_error} errors, {cnt_warn} warnings, {chk_str}{= cnt_lines} lines checked") + + if quiet =3D=3D 0: + if not clean and not fix: + print("\nNOTE: For some of the reported defects, checkpatch ma= y be able to\n" + " mechanically convert to the typical style using -= -fix or --fix-inplace.") + if rpt_cleaners: + print("\nNOTE: Whitespace errors detected.\n" + " You may wish to use scripts/cleanpatch or scripts= /cleanfile") + + if clean =3D=3D 0 and fix and fixed !=3D rawlines: + newfile =3D filename + if not fix_inplace: + newfile +=3D '.EXPERIMENTAL-checkpatch-fixes' + try: + with open(newfile, 'w', encoding=3D'utf-8') as f: + linecount =3D 0 + for fixed_line in fixed: + linecount +=3D 1 + if file_mode: + if linecount > 3: + f.write(re.sub(r'^\+', '', fixed_line) + '\n') + else: + f.write(fixed_line + '\n') + if not quiet: + print(f"\nWrote EXPERIMENTAL --fix correction(s) to '{newf= ile}'\n\n" + "Do _NOT_ trust the results written to this file.\n" + "Do _NOT_ submit these changes without inspecting th= em for correctness.\n\n" + "This EXPERIMENTAL file is simply a convenience to h= elp rewrite patches.\n" + "No warranties, expressed or implied...") + except IOError as e: + print(f"{P}: Can't open {newfile} for write: {e}", file=3Dsys.= stderr) + + if quiet =3D=3D 0: + print() + if clean =3D=3D 1: + print(f"{vname} has no obvious style problems and is ready for= submission.") + else: + print(f"{vname} has style problems, please review.") + + return clean + +# ---- CLI + main ---- + +color_enabled =3D False + +def hash_save_array_words(hash_ref, array_ref): + for item in array_ref: + for word in item.split(','): + word =3D word.strip().upper() + if word and not word.startswith('#'): + hash_ref[word] =3D hash_ref.get(word, 0) + 1 + +def main(): + global P, quiet, verbose, tree, chk_signoff, chk_fixes_tag, chk_patch + global tst_only, emacs, terse, showfile, file_mode, git_mode + global check, check_orig, summary, mailback, summary_file + global show_types, list_types, fix, fix_inplace, root, debug + global use_type, ignore_type, max_line_length, min_conf_desc_length + global codespell, codespellfile, user_codespellfile, typedefsfile + global color, allow_c99_comments, tabsize, CONFIG_ + global color_enabled, rawlines, lines, vname, gitroot + global use_list, ignore_list + + # Load config file + conf =3D which_conf(configuration_file) + conf_args =3D [] + if conf and os.path.isfile(conf): + try: + with open(conf, 'r', encoding=3D'utf-8') as f: + for line in f: + line =3D line.strip() + line =3D re.sub(r'\s+', ' ', line) + if not line or line.startswith('#'): + continue + for word in line.split(): + if word.startswith('#'): + break + conf_args.append(word) + except IOError: + pass + + parser =3D argparse.ArgumentParser(add_help=3DFalse) + parser.add_argument('-q', '--quiet', action=3D'count', default=3D0) + parser.add_argument('-v', '--verbose', action=3D'store_true', default= =3DFalse) + parser.add_argument('--tree', action=3D'store_true', default=3DTrue) + parser.add_argument('--no-tree', dest=3D'tree', action=3D'store_false') + parser.add_argument('--signoff', action=3D'store_true', default=3DTrue) + parser.add_argument('--no-signoff', dest=3D'signoff', action=3D'store_= false') + parser.add_argument('--fixes-tag', action=3D'store_true', default=3DTr= ue) + parser.add_argument('--no-fixes-tag', dest=3D'fixes_tag', action=3D'st= ore_false') + parser.add_argument('--patch', action=3D'store_true', default=3DTrue) + parser.add_argument('--no-patch', dest=3D'patch', action=3D'store_fals= e') + parser.add_argument('--emacs', action=3D'store_true', default=3DFalse) + parser.add_argument('--terse', action=3D'store_true', default=3DFalse) + parser.add_argument('--showfile', action=3D'store_true', default=3DFal= se) + parser.add_argument('-f', '--file', dest=3D'file_mode', action=3D'stor= e_true', default=3DFalse) + parser.add_argument('-g', '--git', dest=3D'git_mode', action=3D'store_= true', default=3DFalse) + parser.add_argument('--subjective', '--strict', dest=3D'strict', actio= n=3D'store_true', default=3DFalse) + parser.add_argument('--list-types', action=3D'store_true', default=3DF= alse) + parser.add_argument('--types', action=3D'append', default=3D[]) + parser.add_argument('--ignore', action=3D'append', default=3D[]) + parser.add_argument('--show-types', action=3D'store_true', default=3DF= alse) + parser.add_argument('--max-line-length', type=3Dint, default=3D100) + parser.add_argument('--min-conf-desc-length', type=3Dint, default=3D4) + parser.add_argument('--tab-size', type=3Dint, default=3D8) + parser.add_argument('--root', default=3DNone) + parser.add_argument('--summary', action=3D'store_true', default=3DTrue) + parser.add_argument('--no-summary', dest=3D'summary', action=3D'store_= false') + parser.add_argument('--mailback', action=3D'store_true', default=3DFal= se) + parser.add_argument('--summary-file', action=3D'store_true', default= =3DFalse) + parser.add_argument('--fix', action=3D'store_true', default=3DFalse) + parser.add_argument('--fix-inplace', action=3D'store_true', default=3D= False) + parser.add_argument('--debug', action=3D'append', default=3D[]) + parser.add_argument('--test-only', default=3DNone) + parser.add_argument('--codespell', action=3D'store_true', default=3DFa= lse) + parser.add_argument('--codespellfile', default=3D'') + parser.add_argument('--typedefsfile', default=3DNone) + parser.add_argument('--color', nargs=3D'?', const=3D'auto', default=3D= 'auto') + parser.add_argument('--no-color', dest=3D'color', action=3D'store_cons= t', const=3D'never') + parser.add_argument('--nocolor', dest=3D'color', action=3D'store_const= ', const=3D'never') + parser.add_argument('--kconfig-prefix', default=3D'CONFIG_') + parser.add_argument('-h', '--help', '--version', action=3D'store_true'= , dest=3D'show_help', default=3DFalse) + parser.add_argument('files', nargs=3D'*') + + args =3D parser.parse_args(conf_args + sys.argv[1:]) + + if args.show_help: + print(f"Usage: {P} [OPTION]... [FILE]...") + print(f"Version: {V}") + print(""" +Options: + -q, --quiet quiet + -v, --verbose verbose mode + --no-tree run without a kernel tree + --no-signoff do not check for 'Signed-off-by' line + --patch treat FILE as patchfile (default) + --emacs emacs compile window format + --terse one line per report + -f, --file treat FILE as regular source file + -g, --git treat FILE as a single commit or git revision= range + --subjective, --strict enable more subjective tests + --list-types list the possible message types + --types TYPE(,TYPE2...) show only these comma separated message types + --ignore TYPE(,TYPE2...) ignore various comma separated message types + --show-types show the specific message type in the output + --max-line-length=3Dn set the maximum line length (default 100) + --root=3DPATH PATH to the kernel tree root + --fix EXPERIMENTAL - create fix file + --fix-inplace EXPERIMENTAL - fix in place + --codespell Use the codespell dictionary + --color[=3DWHEN] Use colors 'always', 'never', or 'auto' (de= fault) + -h, --help, --version display this help and exit""") + sys.exit(0) + + quiet =3D args.quiet + verbose =3D args.verbose + tree =3D args.tree + chk_signoff =3D args.signoff + chk_fixes_tag =3D args.fixes_tag + chk_patch =3D args.patch + emacs =3D args.emacs + terse =3D args.terse + showfile =3D args.showfile + file_mode =3D args.file_mode + git_mode =3D args.git_mode + check =3D args.strict + summary =3D args.summary + mailback =3D args.mailback + summary_file =3D args.summary_file + fix =3D args.fix + fix_inplace =3D args.fix_inplace + show_types =3D args.show_types + list_types =3D args.list_types + max_line_length =3D args.max_line_length + min_conf_desc_length =3D args.min_conf_desc_length + tabsize =3D args.tab_size + root =3D args.root + tst_only =3D args.test_only + codespell =3D args.codespell + typedefsfile =3D args.typedefsfile + CONFIG_ =3D args.kconfig_prefix + color =3D args.color + + if args.codespellfile: + codespellfile =3D args.codespellfile + user_codespellfile =3D args.codespellfile + + # Process debug + for d in args.debug: + if '=3D' in d: + k, v =3D d.split('=3D', 1) + debug[k] =3D v + + # Process types/ignore + hash_save_array_words(ignore_type, args.ignore) + hash_save_array_words(use_type, args.types) + + # Color setup + if color in ('0', '1'): + color_enabled =3D (color =3D=3D '0') # inverted like Perl + elif color.lower() =3D=3D 'always': + color_enabled =3D True + elif color.lower() =3D=3D 'never': + color_enabled =3D False + elif color.lower() =3D=3D 'auto': + color_enabled =3D sys.stdout.isatty() + else: + print(f"{P}: Invalid color mode: {color}", file=3Dsys.stderr) + sys.exit(1) + + if verbose: + load_docs() + if list_types: + do_list_types() + sys.exit(0) + + if fix_inplace: + fix =3D True + check_orig =3D check + + if git_mode and (file_mode or fix): + print(f"{P}: --git cannot be used with --file or --fix", file=3Dsy= s.stderr) + sys.exit(1) + if verbose and terse: + print(f"{P}: --verbose cannot be used with --terse", file=3Dsys.st= derr) + sys.exit(1) + + if terse: + emacs =3D True + quiet +=3D 1 + + if tabsize < 2: + print(f"{P}: Invalid TAB size: {tabsize}", file=3Dsys.stderr) + sys.exit(1) + + # Find kernel tree root + if tree: + if root: + if not top_of_kernel_tree(root): + print(f"{P}: {root}: --root does not point at a valid tree= ", file=3Dsys.stderr) + sys.exit(1) + else: + if top_of_kernel_tree('.'): + root =3D '.' + else: + # Try to find root from script location + script_dir =3D os.path.dirname(os.path.abspath(sys.argv[0]= )) + parent =3D os.path.dirname(script_dir) + if top_of_kernel_tree(parent): + root =3D parent + + if not root: + print("Must be run from the top-level dir. of a kernel tree", = file=3Dsys.stderr) + sys.exit(2) + + if file_mode: + chk_signoff =3D False + chk_fixes_tag =3D False + + allow_c99_comments =3D 'C99_COMMENT_TOLERANCE' not in ignore_type + + # Load spelling data + load_spelling() + load_const_structs() + + # Handle input files + input_files =3D args.files if args.files else ['-'] + + # Handle git mode + if git_mode: + if not os.path.exists(gitroot): + print(f"{P}: No git repository found", file=3Dsys.stderr) + sys.exit(1) + commits =3D [] + for commit_expr in input_files: + if re.match(r'^(.*)-(\d+)$', commit_expr): + m =3D re.match(r'^(.*)-(\d+)$', commit_expr) + git_range =3D f"-{m.group(2)} {m.group(1)}" + elif '..' in commit_expr: + git_range =3D commit_expr + else: + git_range =3D f"-1 {commit_expr}" + try: + output =3D subprocess.run( + f"{git_command} log --no-color --no-merges --pretty=3D= format:'%H %s' {git_range}", + shell=3DTrue, capture_output=3DTrue, text=3DTrue).stdo= ut + for line in output.split('\n'): + m =3D re.match(r'^([0-9a-fA-F]{40}) (.*)$', line) + if m: + sha1 =3D m.group(1) + subject =3D m.group(2) + commits.insert(0, sha1) + git_commits[sha1] =3D subject + except Exception: + pass + + if not commits: + print(f"{P}: no git commits after extraction!", file=3Dsys.std= err) + sys.exit(1) + input_files =3D commits + + exit_code =3D 0 + + for filename in input_files: + rawlines =3D [] + is_git_file =3D git_is_single_file(filename) + old_file_mode =3D file_mode + if is_git_file: + file_mode =3D True + + if git_mode: + try: + proc =3D subprocess.run(f"git format-patch -M --stdout -1 = {filename}", + shell=3DTrue, capture_output=3DTrue,= text=3DTrue) + rawlines =3D proc.stdout.rstrip('\n').split('\n') + except Exception as e: + print(f"{P}: {filename}: git format-patch failed - {e}", f= ile=3Dsys.stderr) + sys.exit(1) + elif file_mode: + try: + proc =3D subprocess.run(f"diff -u /dev/null {filename}", + shell=3DTrue, capture_output=3DTrue,= text=3DTrue) + rawlines =3D proc.stdout.rstrip('\n').split('\n') + except Exception as e: + print(f"{P}: {filename}: diff failed - {e}", file=3Dsys.st= derr) + sys.exit(1) + elif filename =3D=3D '-': + rawlines =3D [line.rstrip('\n') for line in sys.stdin] + else: + try: + with open(filename, 'r', encoding=3D'utf-8', errors=3D'rep= lace') as f: + rawlines =3D [line.rstrip('\n') for line in f] + except IOError as e: + print(f"{P}: {filename}: open failed - {e}", file=3Dsys.st= derr) + sys.exit(1) + + if filename =3D=3D '-': + vname =3D 'Your patch' + elif git_mode: + vname =3D f'Commit {filename[:12]} ("{git_commits.get(filename= , "")}")' + else: + vname =3D filename + + # Check Subject line for vname + for rl in rawlines: + m =3D re.match(r'^Subject:\s+(.+)', rl, re.IGNORECASE) + if m and filename =3D=3D '-': + vname =3D f'"{m.group(1)}"' + + if len(input_files) > 1 and quiet =3D=3D 0: + print('-' * len(vname)) + print(vname) + print('-' * len(vname)) + + if not process(filename): + exit_code =3D 1 + + rawlines =3D [] + lines =3D [] + fixed =3D [] + modifierListFile =3D [] + typeListFile =3D [] + build_types() + if is_git_file: + file_mode =3D old_file_mode + + if not quiet: + if use_type: + print("\nNOTE: Used message types: " + ' '.join(sorted(use_typ= e.keys()))) + if ignore_type: + print("\nNOTE: Ignored message types: " + ' '.join(sorted(igno= re_type.keys()))) + if exit_code: + print("\nNOTE: If any of the errors are false positives, pleas= e report\n" + " them to the maintainer, see CHECKPATCH in MAINTAI= NERS.") + + sys.exit(exit_code) + +if __name__ =3D=3D '__main__': + main() --=20 2.52.0