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