1cdf0c1d5Smjnelson#! /usr/bin/python
2cdf0c1d5Smjnelson#
3cdf0c1d5Smjnelson# CDDL HEADER START
4cdf0c1d5Smjnelson#
5cdf0c1d5Smjnelson# The contents of this file are subject to the terms of the
6cdf0c1d5Smjnelson# Common Development and Distribution License (the "License").
7cdf0c1d5Smjnelson# You may not use this file except in compliance with the License.
8cdf0c1d5Smjnelson#
9cdf0c1d5Smjnelson# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10cdf0c1d5Smjnelson# or http://www.opensolaris.org/os/licensing.
11cdf0c1d5Smjnelson# See the License for the specific language governing permissions
12cdf0c1d5Smjnelson# and limitations under the License.
13cdf0c1d5Smjnelson#
14cdf0c1d5Smjnelson# When distributing Covered Code, include this CDDL HEADER in each
15cdf0c1d5Smjnelson# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16cdf0c1d5Smjnelson# If applicable, add the following below this CDDL HEADER, with the
17cdf0c1d5Smjnelson# fields enclosed by brackets "[]" replaced with your own identifying
18cdf0c1d5Smjnelson# information: Portions Copyright [yyyy] [name of copyright owner]
19cdf0c1d5Smjnelson#
20cdf0c1d5Smjnelson# CDDL HEADER END
21cdf0c1d5Smjnelson#
22cdf0c1d5Smjnelson
23cdf0c1d5Smjnelson#
24cdf0c1d5Smjnelson# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
25cdf0c1d5Smjnelson# Use is subject to license terms.
26cdf0c1d5Smjnelson#
27*2f54b716SRichard Lowe
28*2f54b716SRichard Lowe# Copyright 2008, 2010, Richard Lowe
29cdf0c1d5Smjnelson
30cdf0c1d5Smjnelson#
31cdf0c1d5Smjnelson# Check that header files conform to our standards
32cdf0c1d5Smjnelson#
33cdf0c1d5Smjnelson# Standards for all header files (lenient):
34cdf0c1d5Smjnelson#
35cdf0c1d5Smjnelson#       1) Begin with a comment containing a copyright message
36cdf0c1d5Smjnelson#
37cdf0c1d5Smjnelson#       2) Enclosed in a guard of the form:
38cdf0c1d5Smjnelson#
39cdf0c1d5Smjnelson#          #ifndef GUARD
40cdf0c1d5Smjnelson#          #define GUARD
41cdf0c1d5Smjnelson#          #endif /* [!]GUARD */
42cdf0c1d5Smjnelson#
43cdf0c1d5Smjnelson#          The preferred form is without the bang character, but either is
44cdf0c1d5Smjnelson#          acceptable.
45cdf0c1d5Smjnelson#
46cdf0c1d5Smjnelson#       3) Has a valid ident declaration
47cdf0c1d5Smjnelson#
48cdf0c1d5Smjnelson# Additional standards for system header files:
49cdf0c1d5Smjnelson#
50cdf0c1d5Smjnelson#       1) The file guard must take the form '_FILENAME_H[_]', where FILENAME
51cdf0c1d5Smjnelson#          matches the basename of the file.  If it is installed in a
52cdf0c1d5Smjnelson#          subdirectory, it must be of the form _DIR_FILENAME_H.  The form
53cdf0c1d5Smjnelson#          without the trailing underscore is preferred.
54cdf0c1d5Smjnelson#
55cdf0c1d5Smjnelson#       2) All #include directives must use the <> form.
56cdf0c1d5Smjnelson#
57cdf0c1d5Smjnelson#       3) If the header file contains anything besides comments and
58cdf0c1d5Smjnelson#          preprocessor directives, then it must be enclosed in a C++ guard of
59cdf0c1d5Smjnelson#          the form:
60cdf0c1d5Smjnelson#
61cdf0c1d5Smjnelson#          #ifdef __cplusplus
62cdf0c1d5Smjnelson#          extern "C" {
63cdf0c1d5Smjnelson#          #endif
64cdf0c1d5Smjnelson#
65cdf0c1d5Smjnelson#          #ifdef __cplusplus
66cdf0c1d5Smjnelson#          }
67cdf0c1d5Smjnelson#          #endif
68cdf0c1d5Smjnelson#
69cdf0c1d5Smjnelson
70cdf0c1d5Smjnelsonimport re, os, sys
71*2f54b716SRichard Lowefrom onbld.Checks.Copyright import is_copyright
72cdf0c1d5Smjnelson
73cdf0c1d5Smjnelsonclass HeaderFile(object):
74cdf0c1d5Smjnelson	def __init__(self, fh, filename=None, lenient=False):
75cdf0c1d5Smjnelson		self.file = fh
76cdf0c1d5Smjnelson		self.lenient = lenient
77cdf0c1d5Smjnelson		self.lineno = 0
78cdf0c1d5Smjnelson		self.has_copyright = False
79cdf0c1d5Smjnelson		self.eof = False
80cdf0c1d5Smjnelson
81cdf0c1d5Smjnelson		if filename:
82cdf0c1d5Smjnelson			self.filename = filename
83cdf0c1d5Smjnelson		else:
84cdf0c1d5Smjnelson			self.filename = fh.name
85cdf0c1d5Smjnelson
86cdf0c1d5Smjnelson	def getline(self):
87cdf0c1d5Smjnelson		for line in self.file:
88cdf0c1d5Smjnelson			self.lineno += 1
89cdf0c1d5Smjnelson			if not line or line.isspace():
90cdf0c1d5Smjnelson				continue
91cdf0c1d5Smjnelson			else:
92cdf0c1d5Smjnelson				line = line.rstrip('\r\n')
93cdf0c1d5Smjnelson
94cdf0c1d5Smjnelson				# Recursively join continuation lines
95cdf0c1d5Smjnelson				if line.endswith('\\'):
96cdf0c1d5Smjnelson					line = line[0:-1] + self.getline()
97cdf0c1d5Smjnelson
98cdf0c1d5Smjnelson				return line
99cdf0c1d5Smjnelson		else:
100cdf0c1d5Smjnelson			self.eof = True
101cdf0c1d5Smjnelson			return ''
102cdf0c1d5Smjnelson
103cdf0c1d5Smjnelson	#
104cdf0c1d5Smjnelson	# Optionally take a line to start skipping/processing with
105cdf0c1d5Smjnelson	#
106cdf0c1d5Smjnelson	def skipcomments(self, curline=None):
107cdf0c1d5Smjnelson		line = curline or self.getline()
108cdf0c1d5Smjnelson		while line:
109cdf0c1d5Smjnelson			# When lenient, allow C++ comments
110cdf0c1d5Smjnelson			if self.lenient and re.search(r'^\s*//', line):
111cdf0c1d5Smjnelson				line = self.getline()
112cdf0c1d5Smjnelson				continue
113cdf0c1d5Smjnelson
114cdf0c1d5Smjnelson			if not re.search(r'^\s*/\*', line):
115cdf0c1d5Smjnelson				return line
116cdf0c1d5Smjnelson
117cdf0c1d5Smjnelson			while not re.search(r'\*/', line):
118cdf0c1d5Smjnelson				#
119cdf0c1d5Smjnelson				# We explicitly exclude the form used in the
120cdf0c1d5Smjnelson				# CDDL header rather than attempting to craft
121cdf0c1d5Smjnelson				# a match for every possibly valid copyright
122cdf0c1d5Smjnelson				# notice
123cdf0c1d5Smjnelson				#
124*2f54b716SRichard Lowe				if is_copyright(line):
125cdf0c1d5Smjnelson					self.has_copyright = True
126cdf0c1d5Smjnelson				line = self.getline()
127cdf0c1d5Smjnelson
128*2f54b716SRichard Lowe			if is_copyright(line):
129cdf0c1d5Smjnelson				self.has_copyright = True
130cdf0c1d5Smjnelson			line = self.getline()
131cdf0c1d5Smjnelson
132cdf0c1d5Smjnelson		return line
133cdf0c1d5Smjnelson
134cdf0c1d5Smjnelson
135cdf0c1d5Smjnelsondef err(stream, msg, hdr):
136cdf0c1d5Smjnelson	if not hdr.eof:
137cdf0c1d5Smjnelson		stream.write("%s: line %d: %s\n" %
138cdf0c1d5Smjnelson			     (hdr.filename, hdr.lineno, msg))
139cdf0c1d5Smjnelson	else:
140cdf0c1d5Smjnelson		stream.write("%s: %s\n" % (hdr.filename, msg))
141cdf0c1d5Smjnelson
142cdf0c1d5Smjnelson
143cdf0c1d5Smjnelson#
144cdf0c1d5Smjnelson# Keyword strings (both expanded and literal) for the various SCMs
145cdf0c1d5Smjnelson# Be certain to wrap each full expression in parens.
146cdf0c1d5Smjnelson#
147cdf0c1d5Smjnelsonidents = [
148cdf0c1d5Smjnelson	# SCCS
149cdf0c1d5Smjnelson	r'((\%Z\%(\%M\%)\t\%I\%|\%W\%)\t\%E\% SMI)',
150cdf0c1d5Smjnelson	r'(@\(#\)(\w[-\.\w]+\.h)\t\d+\.\d+(\.\d+\.\d+)?\t\d\d/\d\d/\d\d SMI)',
151cdf0c1d5Smjnelson]
152cdf0c1d5Smjnelson
153cdf0c1d5SmjnelsonIDENT = re.compile(r'(%s)' % '|'.join(idents))
154cdf0c1d5Smjnelson
155cdf0c1d5Smjnelson
156cdf0c1d5Smjnelsondef hdrchk(fh, filename=None, lenient=False, output=sys.stderr):
157cdf0c1d5Smjnelson	found_ident = False
158cdf0c1d5Smjnelson	guard = None
159cdf0c1d5Smjnelson	ret = 0
160cdf0c1d5Smjnelson
161cdf0c1d5Smjnelson	hdr = HeaderFile(fh, filename=filename, lenient=lenient)
162cdf0c1d5Smjnelson
163cdf0c1d5Smjnelson	#
164cdf0c1d5Smjnelson	# Step 1:
165cdf0c1d5Smjnelson	#
166cdf0c1d5Smjnelson	# Headers must begin with a comment containing a copyright notice.  We
167cdf0c1d5Smjnelson	# don't validate the contents of the copyright, only that it exists
168cdf0c1d5Smjnelson	#
169cdf0c1d5Smjnelson	line = hdr.skipcomments()
170cdf0c1d5Smjnelson
171cdf0c1d5Smjnelson	if not hdr.has_copyright:
172cdf0c1d5Smjnelson		err(output, "Missing copyright in opening comment", hdr)
173cdf0c1d5Smjnelson		ret = 1
174*2f54b716SRichard Lowe
175cdf0c1d5Smjnelson	#
176cdf0c1d5Smjnelson	# Step 2:
177cdf0c1d5Smjnelson	#
178cdf0c1d5Smjnelson	# For application header files only, allow the ident string to appear
179cdf0c1d5Smjnelson	# before the header guard.
180cdf0c1d5Smjnelson	if lenient and line.startswith("#pragma ident") and IDENT.search(line):
181cdf0c1d5Smjnelson		found_ident = 1
182cdf0c1d5Smjnelson		line = hdr.skipcomments()
183cdf0c1d5Smjnelson
184cdf0c1d5Smjnelson	#
185cdf0c1d5Smjnelson	# Step 3: Header guards
186cdf0c1d5Smjnelson	#
187cdf0c1d5Smjnelson	match = re.search(r'^#ifndef\s([a-zA-Z0-9_]+)$', line)
188cdf0c1d5Smjnelson	if not match:
189cdf0c1d5Smjnelson		err(output, "Invalid or missing header guard", hdr)
190cdf0c1d5Smjnelson		ret = 1
191cdf0c1d5Smjnelson	else:
192cdf0c1d5Smjnelson		guard = match.group(1)
193cdf0c1d5Smjnelson
194cdf0c1d5Smjnelson		if not lenient:
195cdf0c1d5Smjnelson			guardname = os.path.basename(hdr.filename)
196cdf0c1d5Smjnelson
197cdf0c1d5Smjnelson			#
198cdf0c1d5Smjnelson			# If we aren't being lenient, validate the name of the
199cdf0c1d5Smjnelson			# guard
200cdf0c1d5Smjnelson			#
201cdf0c1d5Smjnelson
202cdf0c1d5Smjnelson			guardname = guardname.upper()
203cdf0c1d5Smjnelson			guardname = guardname.replace('.', '_').replace('-','_')
204cdf0c1d5Smjnelson			guardname = guardname.replace('+', "_PLUS")
205cdf0c1d5Smjnelson
206cdf0c1d5Smjnelson			if not re.search(r'^_.*%s[_]?$' % guardname, guard):
207cdf0c1d5Smjnelson				err(output, "Header guard does not match "
208cdf0c1d5Smjnelson				    "suggested style (_FILEPATH_H_)", hdr)
209cdf0c1d5Smjnelson				ret = 1
210cdf0c1d5Smjnelson
211cdf0c1d5Smjnelson		line = hdr.getline()
212cdf0c1d5Smjnelson		if not re.search(r'#define\s%s$' % guard, line):
213cdf0c1d5Smjnelson			err(output, "Invalid header guard", hdr)
214cdf0c1d5Smjnelson			ret = 1
215cdf0c1d5Smjnelson			if not line:
216cdf0c1d5Smjnelson				line = hdr.skipcomments()
217cdf0c1d5Smjnelson		else:
218cdf0c1d5Smjnelson			line = hdr.skipcomments()
219cdf0c1d5Smjnelson
220cdf0c1d5Smjnelson
221cdf0c1d5Smjnelson	#
222cdf0c1d5Smjnelson	# Step 4: ident string
223cdf0c1d5Smjnelson	#
224cdf0c1d5Smjnelson	# We allow both the keyword and extracted versions
225cdf0c1d5Smjnelson	#
226cdf0c1d5Smjnelson	if (not found_ident and line.startswith("#pragma ident") and
227cdf0c1d5Smjnelson	    not IDENT.search(line)):
228cdf0c1d5Smjnelson		err(output, "Invalid #pragma ident", hdr)
229cdf0c1d5Smjnelson		ret = 1
230cdf0c1d5Smjnelson	else:
231cdf0c1d5Smjnelson		line = hdr.skipcomments(line)
232cdf0c1d5Smjnelson
233cdf0c1d5Smjnelson	#
234cdf0c1d5Smjnelson	# Main processing loop
235cdf0c1d5Smjnelson	#
236cdf0c1d5Smjnelson	in_cplusplus = False
237cdf0c1d5Smjnelson	found_endguard = False
238cdf0c1d5Smjnelson	found_cplusplus = False
239cdf0c1d5Smjnelson	found_code = False
240cdf0c1d5Smjnelson
241cdf0c1d5Smjnelson	while line:
242cdf0c1d5Smjnelson		if not (line.startswith('#') or line.startswith('using')):
243cdf0c1d5Smjnelson			found_code = True
244cdf0c1d5Smjnelson			line = hdr.getline()
245cdf0c1d5Smjnelson			continue
246cdf0c1d5Smjnelson
247cdf0c1d5Smjnelson		match = re.search(r'^#include(.*)$', line)
248cdf0c1d5Smjnelson		if match:
249cdf0c1d5Smjnelson			#
250cdf0c1d5Smjnelson			# For system files, make sure #includes are of the form:
251cdf0c1d5Smjnelson			# '#include <file>'
252cdf0c1d5Smjnelson			#
253cdf0c1d5Smjnelson			if not lenient and not re.search(r'\s<.*>',
254cdf0c1d5Smjnelson							 match.group(1)):
255cdf0c1d5Smjnelson				err(output, "Bad include", hdr)
256cdf0c1d5Smjnelson				ret = 1
257cdf0c1d5Smjnelson		elif not in_cplusplus and re.search(r'^#ifdef\s__cplusplus$',
258cdf0c1d5Smjnelson						    line):
259cdf0c1d5Smjnelson			#
260cdf0c1d5Smjnelson			# Start of C++ header guard.
261cdf0c1d5Smjnelson			# Make sure it is of the form:
262cdf0c1d5Smjnelson			#
263cdf0c1d5Smjnelson			# #ifdef __cplusplus
264cdf0c1d5Smjnelson			# extern "C" {
265cdf0c1d5Smjnelson			# #endif
266cdf0c1d5Smjnelson			#
267cdf0c1d5Smjnelson			line = hdr.getline()
268cdf0c1d5Smjnelson			if line == 'extern "C" {':
269cdf0c1d5Smjnelson				line = hdr.getline()
270cdf0c1d5Smjnelson				if line != '#endif':
271cdf0c1d5Smjnelson					err(output, "Bad __cplusplus clause",
272cdf0c1d5Smjnelson					    hdr)
273cdf0c1d5Smjnelson					ret = 1
274cdf0c1d5Smjnelson				else:
275cdf0c1d5Smjnelson					in_cplusplus = True
276cdf0c1d5Smjnelson					found_cplusplus = True
277cdf0c1d5Smjnelson			else:
278cdf0c1d5Smjnelson				continue
279cdf0c1d5Smjnelson		elif in_cplusplus and re.search(r'^#ifdef\s__cplusplus$', line):
280cdf0c1d5Smjnelson			#
281cdf0c1d5Smjnelson			# End of C++ header guard.  Make sure it is of the form:
282cdf0c1d5Smjnelson			#
283cdf0c1d5Smjnelson			# #ifdef __cplusplus
284cdf0c1d5Smjnelson			# }
285cdf0c1d5Smjnelson			# #endif
286cdf0c1d5Smjnelson			#
287cdf0c1d5Smjnelson			line = hdr.getline()
288cdf0c1d5Smjnelson			if line == '}':
289cdf0c1d5Smjnelson				line = hdr.getline()
290cdf0c1d5Smjnelson				if line != '#endif':
291cdf0c1d5Smjnelson					err(output, "Bad __cplusplus clause",
292cdf0c1d5Smjnelson					    hdr)
293cdf0c1d5Smjnelson					ret = 1
294cdf0c1d5Smjnelson				else:
295cdf0c1d5Smjnelson					in_cplusplus = False
296cdf0c1d5Smjnelson			else:
297cdf0c1d5Smjnelson				continue
298cdf0c1d5Smjnelson		elif re.search(r'^#endif\s/\* [!]?%s \*/$' % guard, line):
299cdf0c1d5Smjnelson			#
300cdf0c1d5Smjnelson			# Ending header guard
301cdf0c1d5Smjnelson			#
302cdf0c1d5Smjnelson			found_endguard = True
303cdf0c1d5Smjnelson
304cdf0c1d5Smjnelson		line = hdr.skipcomments()
305cdf0c1d5Smjnelson
306cdf0c1d5Smjnelson	#
307cdf0c1d5Smjnelson	# Check for missing end clauses
308cdf0c1d5Smjnelson	#
309cdf0c1d5Smjnelson	if (not lenient) and (not found_cplusplus) and found_code:
310cdf0c1d5Smjnelson		err(output, "Missing __cplusplus guard", hdr)
311cdf0c1d5Smjnelson		ret = 1
312cdf0c1d5Smjnelson
313cdf0c1d5Smjnelson	if in_cplusplus:
314cdf0c1d5Smjnelson		err(output, "Missing closing #ifdef __cplusplus", hdr)
315cdf0c1d5Smjnelson		ret = 1
316cdf0c1d5Smjnelson
317cdf0c1d5Smjnelson	if not found_endguard:
318cdf0c1d5Smjnelson		err(output, "Missing or invalid ending header guard", hdr)
319cdf0c1d5Smjnelson		ret = 1
320cdf0c1d5Smjnelson
321cdf0c1d5Smjnelson	return ret
322