xref: /freebsd/tools/tools/kdrv/KernelDriver (revision d0b2dbfa0ecf2bbc9709efc5e20baf8e4b44bbbf)
1#!/bin/sh
2# Tcl magic -*- tcl -*- \
3exec tclsh $0 $*
4################################################################################
5#
6# KernelDriver - FreeBSD driver source installer
7#
8################################################################################
9#
10# Copyright (C) 1997
11#      Michael Smith.  All rights reserved.
12#
13# Redistribution and use in source and binary forms, with or without
14# modification, are permitted provided that the following conditions
15# are met:
16# 1. Redistributions of source code must retain the above copyright
17#    notice, this list of conditions and the following disclaimer.
18# 2. Redistributions in binary form must reproduce the above copyright
19#    notice, this list of conditions and the following disclaimer in the
20#    documentation and/or other materials provided with the distribution.
21# 3. Neither the name of the author nor the names of any co-contributors
22#    may be used to endorse or promote products derived from this software
23#    without specific prior written permission.
24#
25# THIS SOFTWARE IS PROVIDED BY Michael Smith AND CONTRIBUTORS ``AS IS'' AND
26# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28# ARE DISCLAIMED.  IN NO EVENT SHALL Michael Smith OR CONTRIBUTORS BE LIABLE
29# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35# SUCH DAMAGE.
36#
37################################################################################
38#
39# KernelDriver provides a means for installing source-form drivers into FreeBSD
40# kernel source trees in an automated fashion.  It can also remove drivers it
41# has installed.
42#
43# Driver information is read from a control file, with the following syntax :
44#
45# description {<text>}		Driver description; used in comments inserted into
46#				files.
47# driver <name>			The name of the driver. (Note that this can't end in .drvinfo :)
48# filei386 <path> <name>	The file <name> in the driver package is installed into
49#				<path> in the kernel source tree.  Files whose names
50#				end in '.c' have an entry added to i386/conf/files.i386.
51# fileconf <path> <name>	The file <name> in the driver package is installed into
52#				<path> in the kernel source tree.  Files whose names
53#				end in '.c' have an entry added to conf/files.
54# optioni386 <name> <hdr>	Adds an entry to i386/conf/options.i386, such that
55#				the option <name> will be placed in the header <hdr>.
56# optionconf <name> <hdr>	Adds an entry to conf/options, such that
57#				the option <name> will be placed in the header <hdr>.
58# linttext			Lines between this and a subsequent 'end' line are added
59#				to the LINT file to provide configuration examples,
60#				comments, etc.
61# end				Ends a text region.
62#
63# Possible additions :
64#
65# patch <name>		Applies the patch contained in <name>; patch is invoked
66#			at the top level of the kernel source tree, and the
67#			patch must apply cleanly (this is checked).
68#
69# option <name> <file>	Adds an entry to i386/conf/options.i386
70#
71# Lines beginning with '#' or blanks are considered comments, except in
72# 'linttext' regions.
73#
74################################################################################
75#
76#
77################################################################################
78
79################################################################################
80# findDrvFile
81#
82# Given (hint), use it to locate a driver information file.
83# (Possible extension; support drivers in gzipped tarballs...)
84#
85proc findDrvFile_try {hint} {
86
87    # points to something already
88    if {[file exists $hint]} {
89	# unwind symbolic links
90	while {[file type $hint] == "link"} {
91	    set hint [file readlink $hint];
92	}
93	switch [file type $hint] {
94	    file {
95		# run with it as it is
96		return $hint;
97	    }
98	    directory {
99		# look for a drvinfo file in the directory
100		set candidate [glob -nocomplain "$hint/*.drvinfo"];
101		switch [llength $candidate] {
102		    0 {
103			# nothing there
104		    }
105		    1 {
106			return $candidate;
107		    }
108		    default {
109			error "multiple driver info files in directory : $hint";
110		    }
111		}
112	    }
113	    default {
114		error "driver info file may be a typewriter : $hint";
115	    }
116	}
117    }
118    # maybe we need an extension
119    if {[file exists $hint.drvinfo]} {
120	return $hint.drvinfo;
121    }
122    error "can't find a driver info file using '$hint'";
123}
124
125proc findDrvFile {hint} {
126
127    set result [findDrvFile_try $hint];
128    if {$result != ""} {
129	return $result;
130    }
131    set result [findDrvFile_try ${hint}.drvinfo];
132    if {$result != ""} {
133	return $result;
134    }
135    error "can't find driver information file using : $hint";
136}
137
138################################################################################
139# readDrvFile
140#
141# Reads the contents of (fname), which are expected to be in the format
142# described above, and fill in the global Drv array.
143#
144proc readDrvFile {fname} {
145
146    global Drv Options;
147
148    if {$Options(verbose)} {puts "+ read options from '$fname'";}
149    set fh [open $fname r];
150
151    # set defaults
152    set Drv(description) "";
153    set Drv(driver) "";
154    set Drv(filesi386) "";
155    set Drv(filesconf) "";
156    set Drv(optionsi386) "";
157    set Drv(optionsconf) "";
158    set Drv(patches) "";
159    set Drv(linttext) "";
160
161    while {[gets $fh line] >= 0} {
162
163	# blank lines/comments
164	if {([llength $line] == 0) ||
165	    ([string index $line 0] == "\#")} {
166	    continue ;
167	}
168
169	# get keyword, process
170	switch -- [lindex $line 0] {
171	    description {
172		set Drv(description) [lindex $line 1];
173	    }
174	    driver {
175		set Drv(driver) [lindex $line 1];
176	    }
177	    filei386 {
178		set path [lindex $line 1];
179		set plast [expr [string length $path] -1];
180		if {[string index $path $plast] != "/"} {
181		    append path "/";
182		}
183		set name [lindex $line 2];
184		set Drv(filei386:$name) $path;
185		lappend Drv(filesi386) $name;
186	    }
187	    fileconf {
188		set path [lindex $line 1];
189		set plast [expr [string length $path] -1];
190		if {[string index $path $plast] != "/"} {
191		    append path "/";
192		}
193		set name [lindex $line 2];
194		set Drv(fileconf:$name) $path;
195		lappend Drv(filesconf) $name;
196	    }
197	    optioni386 {
198		set opt [lindex $line 1];
199		set hdr [lindex $line 2];
200		lappend Drv(optionsi386) $opt;
201		set Drv(optioni386:$opt) $hdr;
202	    }
203	    optionconf {
204		set opt [lindex $line 1];
205		set hdr [lindex $line 2];
206		lappend Drv(optionsconf) $opt;
207		set Drv(optionconf:$opt) $hdr;
208	    }
209	    patch {
210		lappend Drv(patches) [lindex $line 1];
211	    }
212	    linttext {
213		while {[gets $fh line] >= 0} {
214		    if {$line == "end"} {
215			break ;
216		    }
217		    lappend Drv(linttext) $line;
218		}
219	    }
220	}
221    }
222    close $fh;
223    if {$Options(verbose)} {
224	printDrv;
225    }
226}
227
228################################################################################
229# validateDrvPackage
230#
231# With the global Drv filled in, check that the files required are all in
232# (dir), and that the kernel config at (kpath) can be written.
233#
234proc validateDrvPackage {dir kpath} {
235
236    global Drv Options;
237
238    if {$Options(verbose)} {puts "+ checking driver package...";}
239    set missing "";
240    set unwritable "";
241
242    # check files, patches
243    foreach f $Drv(filesi386) {
244	if {![file readable $dir$f]} {
245	    lappend missing $f;
246	}
247    }
248    foreach f $Drv(filesconf) {
249	if {![file readable $dir$f]} {
250	    lappend missing $f;
251	}
252    }
253    foreach f $Drv(patches) {
254	if {![file readable $dir$f]} {
255	    lappend missing $f;
256	}
257    }
258    if {$missing != ""} {
259	error "missing files : $missing";
260    }
261
262    # check writability
263    if {$Options(verbose)} {puts "+ checking kernel source writability...";}
264    foreach f $Drv(filesi386) {
265	set p $Drv(filei386:$f);
266	if {![file isdirectory $kpath$p]} {
267	    lappend missing $p;
268	} else {
269	    if {![file writable $kpath$p]} {
270		if {[lsearch -exact $unwritable $p] == -1} {
271		    lappend unwritable $p;
272		}
273	    }
274	}
275    }
276    foreach f $Drv(filesconf) {
277	set p $Drv(fileconf:$f);
278	if {![file isdirectory $kpath$p]} {
279	    lappend missing $p;
280	} else {
281	    if {![file writable $kpath$p]} {
282		if {[lsearch -exact $unwritable $p] == -1} {
283		    lappend unwritable $p;
284		}
285	    }
286	}
287    }
288    foreach f [list \
289		   "conf/files" \
290		   "i386/conf/files.i386" \
291		   "i386/conf/options.i386" \
292		   "i386/conf/LINT"] {
293	if {![file writable $kpath$f]} {
294	    lappend unwritable $f;
295	}
296    }
297    if {$missing != ""} {
298	error "missing directories : $missing";
299    }
300    if {$unwritable != ""} {
301	error "can't write to : $unwritable";
302    }
303}
304
305################################################################################
306# installDrvFiles
307#
308# Install the files listed in the global Drv into (kpath) from (dir)
309#
310proc installDrvFiles {dir kpath} {
311
312    global Drv Options;
313
314    # clear 'installed' record
315    set Drv(installedi386) "";
316    set Drv(installedconf) "";
317    set failed "";
318
319    if {$Options(verbose)} {puts "+ installing driver files...";}
320    foreach f $Drv(filesi386) {
321	if {$Options(verbose)} {puts "$f -> $kpath$Drv(filei386:$f)";}
322	if {$Options(real)} {
323	    if {[catch {exec cp $dir$f $kpath$Drv(filei386:$f)} msg]} {
324		lappend failed $f;
325	    } else {
326		lappend Drv(installedi386) $f;
327	    }
328	}
329    }
330    foreach f $Drv(filesconf) {
331	if {$Options(verbose)} {puts "$f -> $kpath$Drv(fileconf:$f)";}
332	if {$Options(real)} {
333	    if {[catch {exec cp $dir$f $kpath$Drv(fileconf:$f)} msg]} {
334		lappend failed $f;
335	    } else {
336		lappend Drv(installedconf) $f;
337	    }
338	}
339    }
340    if {$failed != ""} {
341	error "failed to install files : $failed";
342    }
343}
344
345################################################################################
346# backoutDrvChanges
347#
348# Remove files from a failed installation in (kpath)
349#
350proc backoutDrvChanges {kpath} {
351
352    global Drv Options;
353
354    if {$Options(verbose)} {puts "+ backing out installed files...";}
355    # delete installed files
356    foreach f $Drv(installedi386) {
357	exec rm -f $kpath$Drv(filei386:$f)$f;
358    }
359    foreach f $Drv(installedconf) {
360	exec rm -f $kpath$Drv(fileconf:$f)$f;
361    }
362}
363
364################################################################################
365# registerDrvFiles
366#
367# Adds an entry to i386/conf/files.i386 and conf/files for the .c files in the driver.
368# (kpath) points to the kernel.
369#
370# A comment is added to the file preceding the new entries :
371#
372#  ## driver: <drivername>
373#  # <description>
374#  # filei386: <path><file>
375#  <file spec (.c files only)>
376#  ## enddriver
377#
378# We only append to the end of the file.
379#
380# Add linttext to the LINT file.
381# Add options to i386/conf/options.i386 if any are specified
382#
383proc registerDrvFiles {kpath} {
384
385    global Drv Options;
386
387    if {$Options(verbose)} {puts "+ registering installed files...";}
388
389# Add stuff to LINT
390    if {$Drv(linttext) != ""} {
391
392	if {$Options(verbose)} {puts "+ updating LINT...";}
393	if {$Options(real)} {
394	    set fname [format "%si386/conf/LINT" $kpath];
395	    set fh [open $fname a];
396
397	    # header
398	    puts $fh "\#\# driver: $Drv(driver)";
399	    puts $fh "\# $Drv(description)";
400	    foreach l $Drv(linttext) {
401		puts $fh $l;
402	    }
403	    puts $fh "\#\# enddriver";
404	    close $fh;
405	}
406    }
407
408# Do filesi386 stuff
409    if {$Options(real)} {
410	set fname [format "%si386/conf/files.i386" $kpath];
411	set fh [open $fname a];
412
413	# header
414	puts $fh "\#\# driver: $Drv(driver)";
415	puts $fh "\# $Drv(description)";
416	# file information
417	foreach f $Drv(filesi386) {
418	    puts $fh "\# file: $Drv(filei386:$f)$f";
419	    # is it a compilable object?
420	    if {[string match "*.c" $f]} {
421		puts $fh "$Drv(filei386:$f)$f\t\toptional\t$Drv(driver)\tdevice-driver";
422	    }
423	}
424	puts $fh "\#\# enddriver";
425	close $fh;
426    }
427    if {$Drv(optionsi386) != ""} {
428	if {$Options(verbose)} {puts "+ adding options...";}
429	if {$Options(real)} {
430	    set fname [format "%si386/conf/options.i386" $kpath];
431	    set fh [open $fname a];
432
433	    # header
434	    puts $fh "\#\# driver: $Drv(driver)";
435	    puts $fh "\# $Drv(description)";
436	    # options
437	    foreach opt $Drv(optionsi386) {
438		puts $fh "$opt\t$Drv(optioni386:$opt)";
439	    }
440	    puts $fh "\#\# enddriver";
441	    close $fh;
442	}
443    }
444
445# Do filesconf stuff
446    if {$Options(real)} {
447	set fname [format "%sconf/files" $kpath];
448	set fh [open $fname a];
449
450	# header
451	puts $fh "\#\# driver: $Drv(driver)";
452	puts $fh "\# $Drv(description)";
453	# file information
454	foreach f $Drv(filesconf) {
455	    puts $fh "\# file: $Drv(fileconf:$f)$f";
456	    # is it a compilable object?
457	    if {[string match "*.c" $f]} {
458		puts $fh "$Drv(fileconf:$f)$f\t\toptional\t$Drv(driver)\tdevice-driver";
459	    }
460	}
461	puts $fh "\#\# enddriver";
462	close $fh;
463    }
464     if {$Drv(optionsconf) != ""} {
465 	if {$Options(verbose)} {puts "+ adding options...";}
466 	if {$Options(real)} {
467 	    set fname [format "%sconf/options" $kpath];
468 	    set fh [open $fname a];
469
470 	    # header
471 	    puts $fh "\#\# driver: $Drv(driver)";
472 	    puts $fh "\# $Drv(description)";
473 	    # options
474 	    foreach opt $Drv(optionsconf) {
475 		puts $fh "$opt\t$Drv(optionconf:$opt)";
476 	    }
477 	    puts $fh "\#\# enddriver";
478 	    close $fh;
479 	}
480     }
481
482}
483
484################################################################################
485# listInstalledDrv
486#
487# List all drivers recorded as installed, in the kernel at (kpath)
488#
489# XXX : fix me so I understand conf/{options,files} stuff!
490proc listInstalledDrv {kpath} {
491
492    global Drv;
493
494    # pick up all the i386 options information first
495    set fname [format "%si386/conf/options.i386" $kpath];
496    if {![file readable $fname]} {
497	error "not a kernel directory";
498    }
499    set fh [open $fname r];
500
501    while {[gets $fh line] >= 0} {
502
503	# got a driver?
504	if {[scan $line "\#\# driver: %s" driver] == 1} {
505	    # read driver details, ignore
506	    gets $fh line;
507	    # loop reading option details
508	    while {[gets $fh line] >= 0} {
509		# end of driver info
510		if {$line == "\#\# enddriver"} {
511		    break ;
512		}
513		# parse option/header tuple
514		if {[scan $line "%s %s" opt hdr] == 2} {
515		    # remember that this driver uses this option
516		    lappend drivers($driver:optionsi386) $opt;
517		    # remember that this option goes in this header
518		    set optionsi386($opt) $hdr;
519		}
520	    }
521	}
522    }
523    close $fh;
524
525    # pick up all the conf options information first
526    set fname [format "%sconf/options" $kpath];
527    if {![file readable $fname]} {
528	error "not a kernel directory";
529    }
530    set fh [open $fname r];
531
532    while {[gets $fh line] >= 0} {
533
534	# got a driver?
535	if {[scan $line "\#\# driver: %s" driver] == 1} {
536	    # read driver details, ignore
537	    gets $fh line;
538	    # loop reading option details
539	    while {[gets $fh line] >= 0} {
540		# end of driver info
541		if {$line == "\#\# enddriver"} {
542		    break ;
543		}
544		# parse option/header tuple
545		if {[scan $line "%s %s" opt hdr] == 2} {
546		    # remember that this driver uses this option
547		    lappend drivers($driver:optionsconf) $opt;
548		    # remember that this option goes in this header
549		    set optionsconf($opt) $hdr;
550		}
551	    }
552	}
553    }
554    close $fh;
555
556    set fname [format "%si386/conf/files.i386" $kpath];
557    set fh [open $fname r];
558
559    while {[gets $fh line] >= 0} {
560
561	# got a driver?
562	if {[scan $line "\#\# driver: %s" driver] == 1} {
563	    # clear global and reset
564	    catch {unset Drv};
565	    set Drv(driver) $driver;
566	    # read driver details
567	    gets $fh line;
568	    set Drv(description) [string range $line 2 end];
569	    set Drv(filesi386) "";
570	    # options?
571	    if {[info exists drivers($Drv(driver):optionsi386)]} {
572		set Drv(optionsi386) $drivers($Drv(driver):optionsi386);
573		# get pathnames
574		foreach opt $Drv(optionsi386) {
575		    set Drv(optioni386:$opt) $optionsi386($opt);
576		}
577	    }
578	    # loop reading file details
579	    while {[gets $fh line] >= 0} {
580		if {$line == "\#\# enddriver"} {
581		    # print this driver and loop
582		    printDrv;
583		    break ;
584		}
585		if {[scan $line "\# filei386: %s" fpath] == 1} {
586		    set f [file tail $fpath];
587		    set Drv(filei386:$f) "[file dirname $fpath]/";
588		    lappend Drv(filesi386) $f;
589		}
590	    }
591	}
592    }
593    close $fh;
594
595    set fname [format "%sconf/files" $kpath];
596    set fh [open $fname r];
597
598    while {[gets $fh line] >= 0} {
599
600	# got a driver?
601	if {[scan $line "\#\# driver: %s" driver] == 1} {
602	    # clear global and reset
603	    catch {unset Drv};
604	    set Drv(driver) $driver;
605	    # read driver details
606	    gets $fh line;
607	    set Drv(description) [string range $line 2 end];
608	    set Drv(filesconf) "";
609	    # options?
610	    if {[info exists drivers($Drv(driver):optionsconf)]} {
611		set Drv(optionsconf) $drivers($Drv(driver):optionsconf);
612		# get pathnames
613		foreach opt $Drv(optionsconf) {
614		    set Drv(optionconf:$opt) $optionsconf($opt);
615		}
616	    }
617	    # loop reading file details
618	    while {[gets $fh line] >= 0} {
619		if {$line == "\#\# enddriver"} {
620		    # print this driver and loop
621		    printDrv;
622		    break ;
623		}
624		if {[scan $line "\# fileconf: %s" fpath] == 1} {
625		    set f [file tail $fpath];
626		    set Drv(fileconf:$f) "[file dirname $fpath]/";
627		    lappend Drv(filesconf) $f;
628		}
629	    }
630	}
631    }
632    close $fh;
633}
634
635################################################################################
636# printDrv
637#
638# Print the contents of the global Drv.
639#
640proc printDrv {} {
641
642    global Drv Options;
643
644    puts "$Drv(driver) : $Drv(description)";
645    if {$Options(verbose)} {
646	foreach f $Drv(filesi386) {
647	    puts " $Drv(filei386:$f)$f"
648	}
649	foreach f $Drv(filesconf) {
650	    puts " $Drv(fileconf:$f)$f"
651	}
652	if {[info exists Drv(optionsi386)]} {
653	    foreach opt $Drv(optionsi386) {
654		puts " $opt in $Drv(optioni386:$opt)";
655	    }
656	}
657	if {[info exists Drv(optionsconf)]} {
658	    foreach opt $Drv(optionsconf) {
659		puts " $opt in $Drv(optionconf:$opt)";
660	    }
661	}
662    }
663}
664
665################################################################################
666# findInstalledDrv
667#
668# Given a kernel tree at (kpath), get driver details about an installed
669# driver (drvname)
670#
671
672proc findInstalledDrvi386 {drvname kpath} {
673
674    global Drv;
675
676    set fname [format "%si386/conf/files.i386" $kpath];
677    set fh [open $fname r];
678
679    puts "checking i386/conf/files.i386";
680
681    while {[gets $fh line] >= 0} {
682	if {[scan $line "\#\# driver: %s" name] == 1} {
683	    if {$name != $drvname} {
684		continue ;		# not us
685	    }
686	    # read information
687	    set Drv(driver) $drvname;
688	    set line [gets $fh];
689	    set Drv(description) [string range $line 2 end];
690	    set Drv(filesi386) "";
691	    # loop reading file details
692	    while {[gets $fh line] >= 0} {
693		if {$line == "\#\# enddriver"} {
694		    close $fh;
695		    return 1;		# all done
696		}
697		if {[scan $line "\# file: %s" fpath] == 1} {
698		    set f [file tail $fpath];
699		    set Drv(filei386:$f) "[file dirname $fpath]/";
700		    lappend Drv(filesi386) $f;
701		}
702	    }
703	    close $fh;
704	    error "unexpected EOF reading '$fname'";
705	}
706    }
707    close $fh
708
709    return 0;
710}
711
712proc findInstalledDrvconf {drvname kpath} {
713
714    global Drv;
715
716    set fname [format "%sconf/files" $kpath];
717    set fh [open $fname r];
718
719    puts "checking conf/files";
720
721    while {[gets $fh line] >= 0} {
722	if {[scan $line "\#\# driver: %s" name] == 1} {
723	    if {$name != $drvname} {
724		continue ;		# not us
725	    }
726	    # read information
727	    set Drv(driver) $drvname;
728	    set line [gets $fh];
729	    set Drv(description) [string range $line 2 end];
730	    set Drv(filesconf) "";
731	    # loop reading file details
732	    while {[gets $fh line] >= 0} {
733		if {$line == "\#\# enddriver"} {
734		    close $fh;
735		    return 1;		# all done
736		}
737		if {[scan $line "\# file: %s" fpath] == 1} {
738		    set f [file tail $fpath];
739		    set Drv(fileconf:$f) "[file dirname $fpath]/";
740		    lappend Drv(filesconf) $f;
741		}
742	    }
743	    close $fh;
744	    error "unexpected EOF reading '$fname'";
745	}
746    }
747    close $fh
748
749    return 0;
750}
751
752proc findInstalledDrv {drvname kpath} {
753
754    global Drv Options;
755
756    if {$Options(verbose)} {puts "+ look for driver '$drvname' in '$kpath'";}
757
758# Whoops... won't work in a single if statement due to expression shortcircuiting
759    set a [findInstalledDrvi386 $drvname $kpath];
760    set b [findInstalledDrvconf $drvname $kpath];
761    if {$a || $b} {
762	return;
763    }
764
765    error "driver '$drvname' not recorded as installed";
766}
767
768################################################################################
769# validateDrvRemoval
770#
771# Verify that we can remove the driver described in the global Drv installed
772# at (kpath).
773#
774proc validateDrvRemoval {kpath} {
775
776    global Drv Options;
777
778    set missing "";
779    set unwritable "";
780
781    if {$Options(verbose)} {puts "+ checking for removabilty...";}
782
783    # admin files?
784    foreach f [list \
785		   "i386/conf/files.i386" \
786		   "i386/conf/options.i386" \
787		   "i386/conf/LINT" \
788		   "conf/files" \
789		   "conf/options" ] {
790	if {![file exists $kpath$f]} {
791	    lappend missing $kpath$f;
792	} else {
793	    if {![file writable $kpath$f]} {
794		lappend unwritable $f;
795	    }
796	}
797    }
798    # driver components?
799    foreach f $Drv(filesi386) {
800	set p $Drv(filei386:$f);
801	if {![file isdirectory $kpath$p]} {
802	    lappend missing $p;
803	} else {
804	    if {![file writable $kpath$p]} {
805		if {[lsearch -exact $unwritable $p] == -1} {
806		    lappend unwritable $p;
807		}
808	    }
809	}
810    }
811    foreach f $Drv(filesconf) {
812	set p $Drv(fileconf:$f);
813	if {![file isdirectory $kpath$p]} {
814	    lappend missing $p;
815	} else {
816	    if {![file writable $kpath$p]} {
817		if {[lsearch -exact $unwritable $p] == -1} {
818		    lappend unwritable $p;
819		}
820	    }
821	}
822    }
823    if {$missing != ""} {
824	error "files/directories missing : $missing";
825    }
826    if {$unwritable != ""} {
827	error "can't write to : $unwritable";
828    }
829}
830
831################################################################################
832# deleteDrvFiles
833#
834# Delete the files belonging to the driver devfined in the global Drv in
835# the kernel tree at (kpath)
836#
837proc deleteDrvFiles {kpath} {
838
839    global Drv Options;
840
841    if {$Options(verbose)} {puts "+ delete driver files...";}
842
843    # loop deleting files
844    foreach f $Drv(filesi386) {
845	if {$Options(verbose)} {puts "- $Drv(filei386:$f)$f";}
846	if {$Options(real)} {
847	    exec rm $kpath$Drv(filei386:$f)$f;
848	}
849    }
850    foreach f $Drv(filesconf) {
851	if {$Options(verbose)} {puts "- $Drv(fileconf:$f)$f";}
852	if {$Options(real)} {
853	    exec rm $kpath$Drv(fileconf:$f)$f;
854	}
855    }
856}
857
858################################################################################
859# unregisterDrvFiles
860#
861# Remove any mention of the current driver from the files.i386 and LINT
862# files in (ksrc)
863#
864proc unregisterDrvFiles {ksrc} {
865
866    global Drv Options;
867
868    if {$Options(verbose)} {puts "+ deregister driver files...";}
869
870    # don't really do it?
871    if {!$Options(real)} { return ; }
872
873    foreach f [list \
874		   "i386/conf/files.i386" \
875		   "i386/conf/options.i386" \
876		   "i386/conf/LINT" \
877		   "conf/files" \
878		   "conf/options" ] {
879	set ifh [open $ksrc$f r];
880	set ofh [open $ksrc$f.new w];
881	set copying 1;
882
883	while {[gets $ifh line] >= 0} {
884
885	    if {[scan $line "\#\# driver: %s" name] == 1} {
886		if {$name == $Drv(driver)} {
887		    set copying 0;			# don't copy this one
888		}
889	    }
890	    if {$copying} {
891		puts $ofh $line;		# copy through
892	    }
893	    if {$line == "\#\# enddriver"} {	# end of driver detail
894		set copying 1;
895	    }
896	}
897	close $ifh;
898	close $ofh;
899	exec mv $ksrc$f.new $ksrc$f;		# move new over old
900    }
901}
902
903################################################################################
904# usage
905#
906# Remind the user what goes where
907#
908proc usage {} {
909
910    global argv0;
911
912    set progname [file tail $argv0];
913
914    puts stderr "Usage is :";
915    puts stderr "  $progname \[-v -n\] add <drvinfo> \[<kpath>\]";
916    puts stderr "  $progname \[-v -n\] delete <drvname> \[<kpath>\]";
917    puts stderr "  $progname \[-v\] list \[<kpath>\]";
918    puts stderr "  <drvinfo> is a driver info file";
919    puts stderr "  <drvname> is a driver name";
920    puts stderr "  <kpath> is the path to the kernel source (default /sys/)";
921    puts stderr "  -v  be verbose";
922    puts stderr "  -n  don't actually do anything";
923    exit ;
924}
925
926################################################################################
927# getOptions
928#
929# Parse commandline options, return anything that doesn't look like an option
930#
931proc getOptions {} {
932
933    global argv Options;
934
935    set Options(real) 1;
936    set Options(verbose) 0;
937    set ret "";
938
939    for {set index 0} {$index < [llength $argv]} {incr index} {
940
941	switch -- [lindex $argv $index] {
942
943	    -n {
944		set Options(real) 0;		# 'do-nothing' mode
945	    }
946	    -v {
947		set Options(verbose) 1;		# brag
948	    }
949	    default {
950		lappend ret [lindex $argv $index];
951	    }
952	}
953    }
954    return $ret;
955}
956
957################################################################################
958# getKpath
959#
960# Given (hint), return the kernel path.  If (hint) is empty, return /sys.
961# If the kernel path is not a directory, complain and dump the usage.
962#
963proc getKpath {hint} {
964
965    set kpath "";
966
967    # check the kernel path
968    if {$hint == ""} {
969	set kpath "/sys/";
970    } else {
971	set kpath $hint;
972    }
973    if {![file isdirectory $kpath]} {
974	puts "not a directory : $kpath";
975	usage ;
976    }
977    set plast [expr [string length $kpath] -1];
978    if {[string index $kpath $plast] != "/"} {
979	append kpath "/";
980    }
981    return $kpath;
982}
983
984################################################################################
985# main
986#
987# Start somewhere here.
988#
989proc main {} {
990
991    global Options;
992
993    # Work out what we're trying to do
994    set cmdline [getOptions];
995    set mode [lindex $cmdline 0];
996
997    # do stuff
998    switch -- $mode {
999	add {
1000	    set hint [lindex $cmdline 1];
1001	    set kpath [getKpath [lindex $cmdline 2]];
1002
1003	    # check driver file argument
1004	    if {[catch {set drv [findDrvFile $hint]} msg]} {
1005		puts stderr $msg;
1006		usage ;
1007	    }
1008	    if {([file type $drv] != "file") ||
1009		![file readable $drv]} {
1010		puts "can't read driver file : $drv";
1011		usage ;
1012	    }
1013	    set drvdir "[file dirname $drv]/";
1014
1015	    # read driver file
1016	    if {[catch {readDrvFile $drv} msg]} {
1017		puts stderr $msg;
1018		exit ;
1019	    }
1020	    # validate driver
1021	    if {[catch {validateDrvPackage $drvdir $kpath} msg]} {
1022		puts stderr $msg;
1023		exit ;
1024	    }
1025	    # install new files
1026	    if {[catch {installDrvFiles $drvdir $kpath} msg]} {
1027		backoutDrvChanges $kpath;		# oops, unwind
1028		puts stderr $msg;
1029		exit ;
1030	    }
1031	    # register files in config
1032	    if {[catch {registerDrvFiles $kpath} msg]} {
1033		backoutDrvChanges $kpath;		# oops, unwind
1034		puts stderr $msg;
1035		exit ;
1036	    }
1037	}
1038	delete {
1039	    set drv [lindex $cmdline 1];
1040	    set kpath [getKpath [lindex $cmdline 2]];
1041
1042	    if {[string last ".drvinfo" $drv] != -1} {
1043		set drv [string range $drv 0 [expr [string length $drv] - 9]];
1044		puts "Driver name ends in .drvinfo, removing, is now $drv";
1045	    }
1046
1047	    if {[catch {findInstalledDrv $drv $kpath} msg]} {
1048		puts stderr $msg;
1049		exit ;
1050	    }
1051	    if {[catch {validateDrvRemoval $kpath} msg]} {
1052		puts stderr $msg;
1053		exit ;
1054	    }
1055	    if {[catch {unregisterDrvFiles $kpath} msg]} {
1056		puts stderr $msg;
1057		exit ;
1058	    }
1059	    if {[catch {deleteDrvFiles $kpath} msg]} {
1060		puts stderr $msg;
1061		exit ;
1062	    }
1063	}
1064	list {
1065	    set kpath [getKpath [lindex $cmdline 1]];
1066	    if {[catch {listInstalledDrv $kpath} msg]} {
1067		puts stderr "can't list drivers in '$kpath' : $msg";
1068	    }
1069	}
1070	default {
1071	    puts stderr "unknown command '$mode'";
1072	    usage ;
1073	}
1074    }
1075}
1076
1077
1078
1079################################################################################
1080main;
1081