For FreeBSD, the status argument needs to be onestatus.
[openresolv] / resolvconf.in
1 #!/bin/sh
2 # Copyright (c) 2007-2016 Roy Marples
3 # All rights reserved
4
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 #     * Redistributions of source code must retain the above copyright
9 #       notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 #       copyright notice, this list of conditions and the following
12 #       disclaimer in the documentation and/or other materials provided
13 #       with the distribution.
14 #
15 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27 RESOLVCONF="$0"
28 OPENRESOLV_VERSION="3.8.0"
29 SYSCONFDIR=@SYSCONFDIR@
30 LIBEXECDIR=@LIBEXECDIR@
31 VARDIR=@VARDIR@
32 RCDIR=@RCDIR@
33 RESTARTCMD=@RESTARTCMD@
34
35 # Disregard dhcpcd setting
36 unset interface_order state_dir
37
38 # If you change this, change the test in VFLAG and libc.in as well
39 local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1"
40
41 dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*"
42 interface_order="lo lo[0-9]*"
43 name_server_blacklist="0.0.0.0"
44
45 # Support original resolvconf configuration layout
46 # as well as the openresolv config file
47 if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
48         . "$SYSCONFDIR"/resolvconf.conf
49         [ -n "$state_dir" ] && VARDIR="$state_dir"
50 elif [ -d "$SYSCONFDIR/resolvconf" ]; then
51         SYSCONFDIR="$SYSCONFDIR/resolvconf"
52         if [ -f "$SYSCONFDIR"/interface-order ]; then
53                 interface_order="$(cat "$SYSCONFDIR"/interface-order)"
54         fi
55 fi
56 IFACEDIR="$VARDIR/interfaces"
57 METRICDIR="$VARDIR/metrics"
58 PRIVATEDIR="$VARDIR/private"
59 EXCLUSIVEDIR="$VARDIR/exclusive"
60 LOCKDIR="$VARDIR/lock"
61 _PWD="$PWD"
62
63 warn()
64 {
65         echo "$*" >&2
66 }
67
68 error_exit()
69 {
70         echo "$*" >&2
71         exit 1
72 }
73
74 usage()
75 {
76         cat <<-EOF
77         Usage: ${RESOLVCONF##*/} [options] command [argument]
78
79         Inform the system about any DNS updates.
80
81         Commands:
82           -a \$INTERFACE    Add DNS information to the specified interface
83                            (DNS supplied via stdin in resolv.conf format)
84           -d \$INTERFACE    Delete DNS information from the specified interface
85           -h               Show this help cruft
86           -i [\$PATTERN]    Show interfaces that have supplied DNS information
87                    optionally from interfaces that match the specified
88                    pattern
89           -l [\$PATTERN]    Show DNS information, optionally from interfaces
90                            that match the specified pattern
91
92           -u               Run updates from our current DNS information
93
94         Options:
95           -f               Ignore non existant interfaces
96           -m metric        Give the added DNS information a metric
97           -p               Mark the interface as private
98           -x               Mark the interface as exclusive
99
100         Subscriber and System Init Commands:
101           -I               Init the state dir
102           -r \$SERVICE      Restart the system service
103                            (restarting a non-existent or non-running service
104                             should have no output and return 0)
105           -R               Show the system service restart command
106           -v [\$PATTERN]    echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
107                            the console
108           -V [\$PATTERN]    Same as -v, but only uses configuration in
109                            $SYSCONFDIR/resolvconf.conf
110         EOF
111         [ -z "$1" ] && exit 0
112         echo
113         error_exit "$*"
114 }
115
116 # Strip any trailing dot from each name as a FQDN does not belong
117 # in resolv.conf(5)
118 # If you think otherwise, capture a DNS trace and you'll see libc
119 # will strip it regardless.
120 # This also solves setting up duplicate zones in our subscribers.
121 strip_trailing_dots()
122 {
123         local n= d=
124
125         for n; do
126                 printf "$d%s" "${n%.}"
127                 d=" "
128         done
129         printf "\n"
130 }
131
132 # Parse resolv.conf's and make variables
133 # for domain name servers, search name servers and global nameservers
134 parse_resolv()
135 {
136         local line= ns= ds= search= d= n= newns=
137         local new=true iface= private=false p= domain= l= islocal=
138
139         newns=
140
141         while read -r line; do
142                 case "$line" in
143                 "# resolv.conf from "*)
144                         if ${new}; then
145                                 iface="${line#\# resolv.conf from *}"
146                                 new=false
147                                 if [ -e "$PRIVATEDIR/$iface" ]; then
148                                         private=true
149                                 else
150                                         # Allow expansion
151                                         cd "$IFACEDIR"
152                                         private=false
153                                         for p in $private_interfaces; do
154                                                 case "$iface" in
155                                                 "$p"|"$p":*) private=true; break;;
156                                                 esac
157                                         done
158                                 fi
159                         fi
160                         ;;
161                 "nameserver "*)
162                         islocal=false
163                         for l in $local_nameservers; do
164                                 case "${line#* }" in
165                                 $l)
166                                         islocal=true
167                                         echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\""
168                                         break
169                                         ;;
170                                 esac
171                         done
172                         $islocal || ns="$ns${line#* } "
173                         ;;
174                 "domain "*)
175                         search="$(strip_trailing_dots ${line#* })"
176                         if [ -z "$domain" ]; then
177                                 domain="$search"
178                                 echo "DOMAIN=\"$domain\""
179                         fi
180                         ;;
181                 "search "*)
182                         search="$(strip_trailing_dots ${line#* })"
183                         ;;
184                 *)
185                         [ -n "$line" ] && continue
186                         if [ -n "$ns" -a -n "$search" ]; then
187                                 newns=
188                                 for n in $ns; do
189                                         newns="$newns${newns:+,}$n"
190                                 done
191                                 ds=
192                                 for d in $search; do
193                                         ds="$ds${ds:+ }$d:$newns"
194                                 done
195                                 echo "DOMAINS=\"\$DOMAINS $ds\""
196                         fi
197                         echo "SEARCH=\"\$SEARCH $search\""
198                         if ! $private; then
199                                 echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
200                         fi
201                         ns=
202                         search=
203                         new=true
204                         ;;
205                 esac
206         done
207 }
208
209 uniqify()
210 {
211         local result=
212         while [ -n "$1" ]; do
213                 case " $result " in
214                 *" $1 "*);;
215                 *) result="$result $1";;
216                 esac
217                 shift
218         done
219         echo "${result# *}"
220 }
221
222 dirname()
223 {
224         local dir= OIFS="$IFS"
225         local IFS=/
226         set -- $@
227         IFS="$OIFS"
228         if [ -n "$1" ]; then
229                 printf %s .
230         else
231                 shift
232         fi
233         while [ -n "$2" ]; do
234                 printf "/%s" "$1"
235                 shift
236         done
237         printf "\n"
238 }
239
240 config_mkdirs()
241 {
242         local e=0 f d
243         for f; do
244                 [ -n "$f" ] || continue
245                 d="$(dirname "$f")"
246                 if [ ! -d "$d" ]; then
247                         if type install >/dev/null 2>&1; then
248                                 install -d "$d" || e=$?
249                         else
250                                 mkdir "$d" || e=$?
251                         fi
252                 fi
253         done
254         return $e
255 }
256
257 # With the advent of alternative init systems, it's possible to have
258 # more than one installed. So we need to try and guess what one we're
259 # using unless overriden by configure.
260 # Note that restarting a service is a last resort - the subscribers
261 # should make a reasonable attempt to reconfigre the service via some
262 # method, normally SIGHUP.
263 detect_init()
264 {
265         [ -n "$RESTARTCMD" ] && return 0
266
267         # Detect the running init system.
268         # As systemd and OpenRC can be installed on top of legacy init
269         # systems we try to detect them first.
270         local status="@STATUSARG@"
271         : ${status:=status}
272         if [ -x /bin/systemctl -a -S /run/systemd/private ]; then
273                 RESTARTCMD="if /bin/systemctl --quiet is-active; then
274         /bin/systemctl restart \$1.service;
275 fi"
276         elif [ -x /usr/bin/systemctl -a -S /run/systemd/private ]; then
277                 RESTARTCMD="if /usr/bin/systemctl --quiet is-active; then
278         /usr/bin/systemctl restart \$1.service;
279 fi"
280         elif [ -x /sbin/rc-service -a \
281             -s /libexec/rc/init.d/softlevel -o -s /run/openrc/softlevel ]
282         then
283                 RESTARTCMD="/sbin/rc-service -i \$1 -- -Ds restart"
284         elif [ -x /usr/sbin/invoke-rc.d ]; then
285                 RCDIR=/etc/init.d
286                 RESTARTCMD="if /usr/sbin/invoke-rc.d --quiet \$1 status 1>/dev/null 2>&1; then
287         /usr/sbin/invoke-rc.d \$1 restart;
288 fi"
289         elif [ -x /sbin/service ]; then
290                 # Old RedHat
291                 RCDIR=/etc/init.d
292                 RESTARTCMD="if /sbin/service \$1; then
293         /sbin/service \$1 restart;
294 fi"
295         elif [ -x /usr/sbin/service ]; then
296                 # Could be FreeBSD
297                 RESTARTCMD="if /usr/sbin/service \$1 $status 1>/dev/null 2>&1; then
298         /usr/sbin/service \$1 restart;
299 fi"
300         elif [ -x /bin/sv ]; then
301                 RESTARTCMD="/bin/sv try-restart \$1"
302         elif [ -x /usr/bin/sv ]; then
303                 RESTARTCMD="/usr/bin/sv try-restart \$1"
304         elif [ -e /etc/arch-release -a -d /etc/rc.d ]; then
305                 RCDIR=/etc/rc.d
306                 RESTARTCMD="if [ -e /var/run/daemons/\$1 ]; then
307         /etc/rc.d/\$1 restart;
308 fi"
309         elif [ -e /etc/slackware-version -a -d /etc/rc.d ]; then
310                 RESTARTCMD="if /etc/rc.d/rc.\$1 status 1>/dev/null 2>&1; then
311         /etc/rc.d/rc.\$1 restart;
312 fi"
313         elif [ -e /etc/rc.d/rc.subr -a -d /etc/rc.d ]; then
314                 # OpenBSD
315                 RESTARTCMD="if /etc/rc.d/\$1 check 1>/dev/null 2>&1; then
316         /etc/rc.d/\$1 restart;
317 fi"
318         else
319                 for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
320                         [ -d $x ] || continue
321                         RESTARTCMD="if $x/\$1 $status 1>/dev/null 2>&1; then
322         $x/\$1 restart;
323 fi"
324                         break
325                 done
326         fi
327
328         if [ -z "$RESTARTCMD" ]; then
329                 if [ "$NOINIT_WARNED" != true ]; then
330                         warn "could not detect a useable init system"
331                         _NOINIT_WARNED=true
332                 fi
333                 return 1
334         fi
335         _NOINIT_WARNED=
336         return 0
337 }
338
339 echo_resolv()
340 {
341         local line= OIFS="$IFS"
342
343         [ -n "$1" -a -f "$IFACEDIR/$1" ] || return 1
344         echo "# resolv.conf from $1"
345         # Our variable maker works of the fact each resolv.conf per interface
346         # is separated by blank lines.
347         # So we remove them when echoing them.
348         while read -r line; do
349                 IFS="$OIFS"
350                 if [ -n "$line" ]; then
351                         # We need to set IFS here to preserve any whitespace
352                         IFS=''
353                         printf "%s\n" "$line"
354                 fi
355         done < "$IFACEDIR/$1"
356         IFS="$OIFS"
357 }
358
359 list_resolv()
360 {
361         [ -d "$IFACEDIR" ] || return 0
362
363         local report=false list= retval=0 cmd="$1" excl=
364         shift
365
366         case "$IF_EXCLUSIVE" in
367         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
368                 if [ -d "$EXCLUSIVEDIR" ]; then
369                         cd "$EXCLUSIVEDIR"
370                         for i in *; do
371                                 if [ -f "$i" ]; then
372                                         list="${i#* }"
373                                         break
374                                 fi
375                         done
376                 fi
377                 excl=true
378                 ;;
379         *)
380                 excl=false
381                 ;;
382         esac
383
384         # If we have an interface ordering list, then use that.
385         # It works by just using pathname expansion in the interface directory.
386         if [ -n "$1" ]; then
387                 list="$*"
388                 $force || report=true
389         elif ! $excl; then
390                 cd "$IFACEDIR"
391                 for i in $interface_order; do
392                         [ -f "$i" ] && list="$list $i"
393                         for ii in "$i":* "$i".*; do
394                                 [ -f "$ii" ] && list="$list $ii"
395                         done
396                 done
397                 for i in $dynamic_order; do
398                         if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then
399                                 list="$list $i"
400                         fi
401                         for ii in "$i":* "$i".*; do
402                                 if [ -f "$ii" -a ! -e "$METRICDIR/"*" $ii" ]; then
403                                         list="$list $ii"
404                                 fi
405                         done
406                 done
407                 if [ -d "$METRICDIR" ]; then
408                         cd "$METRICDIR"
409                         for i in *; do
410                                 [ -f "$i" ] && list="$list ${i#* }"
411                         done
412                 fi
413                 list="$list *"
414         fi
415
416         cd "$IFACEDIR"
417         retval=1
418         excl=true
419         for i in $(uniqify $list); do
420                 # Only list interfaces which we really have
421                 if ! [ -f "$i" ]; then
422                         if $report; then
423                                 echo "No resolv.conf for interface $i" >&2
424                                 retval=2
425                         fi
426                         continue
427                 fi
428                 
429                 if [ "$cmd" = i -o "$cmd" = "-i" ]; then
430                         printf %s "$i "
431                 else
432                         echo_resolv "$i"
433                         echo
434                 fi
435                 [ $? = 0 -a "$retval" = 1 ] && retval=0
436         done
437         [ "$cmd" = i -o "$cmd" = "-i" ] && echo
438         return $retval
439 }
440
441 list_remove() {
442         local list= e= l= result= found= retval=0
443
444         [ -z "$2" ] && return 0
445         eval list=\"\$$1\"
446         shift
447
448         set -f
449         for e; do
450                 found=false
451                 for l in $list; do
452                         case "$e" in
453                         $l) found=true;;
454                         esac
455                         $found && break
456                 done
457                 if $found; then
458                         retval=$(($retval + 1))
459                 else
460                         result="$result $e"
461                 fi
462         done
463         set +f
464         echo "${result# *}"
465         return $retval
466 }
467
468 echo_prepend()
469 {
470         echo "# Generated by resolvconf"
471         if [ -n "$search_domains" ]; then
472                 echo "search $search_domains"
473         fi
474         for n in $name_servers; do
475                 echo "nameserver $n"
476         done
477         echo
478 }
479
480 echo_append()
481 {
482         echo "# Generated by resolvconf"
483         if [ -n "$search_domains_append" ]; then
484                 echo "search $search_domains_append"
485         fi
486         for n in $name_servers_append; do
487                 echo "nameserver $n"
488         done
489         echo
490 }
491
492 replace()
493 {
494         local r= k= f= v= val= sub=
495
496         while read -r keyword value; do
497                 for r in $replace; do
498                         k="${r%%/*}"
499                         r="${r#*/}"
500                         f="${r%%/*}"
501                         r="${r#*/}"
502                         v="${r%%/*}"
503                         case "$keyword" in
504                         $k)
505                                 case "$value" in
506                                 $f) value="$v";;
507                                 esac
508                                 ;;
509                         esac
510                 done
511                 val=
512                 for sub in $value; do
513                         for r in $replace_sub; do
514                                 k="${r%%/*}"
515                                 r="${r#*/}"
516                                 f="${r%%/*}"
517                                 r="${r#*/}"
518                                 v="${r%%/*}"
519                                 case "$keyword" in
520                                 $k)
521                                         case "$sub" in
522                                         $f) sub="$v";;
523                                         esac
524                                         ;;
525                                 esac
526                         done
527                         val="$val${val:+ }$sub"
528                 done
529                 printf "%s %s\n" "$keyword" "$val"
530         done
531 }
532
533 make_vars()
534 {
535         local newdomains= d= dn= newns= ns=
536
537         # Clear variables
538         DOMAIN=
539         DOMAINS=
540         SEARCH=
541         NAMESERVERS=
542         LOCALNAMESERVERS=
543         
544         if [ -n "$name_servers" -o -n "$search_domains" ]; then
545                 eval "$(echo_prepend | parse_resolv)"
546         fi
547         if [ -z "$VFLAG" ]; then
548                 IF_EXCLUSIVE=1
549                 list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0
550                 eval "$(list_resolv -l "$@" | replace | parse_resolv)"
551         fi
552         if [ -n "$name_servers_append" -o -n "$search_domains_append" ]; then
553                 eval "$(echo_append | parse_resolv)"
554         fi
555
556         # Ensure that we only list each domain once
557         for d in $DOMAINS; do
558                 dn="${d%%:*}"
559                 list_remove domain_blacklist "$dn" >/dev/null || continue
560                 case " $newdomains" in
561                 *" ${dn}:"*) continue;;
562                 esac
563                 newns=
564                 for nd in $DOMAINS; do
565                         if [ "$dn" = "${nd%%:*}" ]; then
566                                 ns="${nd#*:}"
567                                 while [ -n "$ns" ]; do
568                                         case ",$newns," in
569                                         *,${ns%%,*},*) ;;
570                                         *) list_remove name_server_blacklist \
571                                                 "${ns%%,*}" >/dev/null \
572                                         && newns="$newns${newns:+,}${ns%%,*}";;
573                                         esac
574                                         [ "$ns" = "${ns#*,}" ] && break
575                                         ns="${ns#*,}"
576                                 done
577                         fi
578                 done
579                 if [ -n "$newns" ]; then
580                         newdomains="$newdomains${newdomains:+ }$dn:$newns"
581                 fi
582         done
583         DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
584         SEARCH="$(uniqify $SEARCH)"
585         SEARCH="$(list_remove domain_blacklist $SEARCH)"
586         NAMESERVERS="$(uniqify $NAMESERVERS)"
587         NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
588         LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
589         LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
590         echo "DOMAIN='$DOMAIN'"
591         echo "SEARCH='$SEARCH'"
592         echo "NAMESERVERS='$NAMESERVERS'"
593         echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
594         echo "DOMAINS='$newdomains'"
595 }
596
597 force=false
598 VFLAG=
599 while getopts a:Dd:fhIilm:pRruvVx OPT; do
600         case "$OPT" in
601         f) force=true;;
602         h) usage;;
603         m) IF_METRIC="$OPTARG";;
604         p) IF_PRIVATE=1;;
605         V)
606                 VFLAG=1
607                 if [ "$local_nameservers" = \
608                     "127.* 0.0.0.0 255.255.255.255 ::1" ]
609                 then
610                         local_nameservers=
611                 fi
612                 ;;
613         x) IF_EXCLUSIVE=1;;
614         '?') ;;
615         *) cmd="$OPT"; iface="$OPTARG";;
616         esac
617 done
618 shift $(($OPTIND - 1))
619 args="$iface${iface:+ }$*"
620
621 # -I inits the state dir
622 if [ "$cmd" = I ]; then
623         if [ -d "$VARDIR" ]; then
624                 rm -rf "$VARDIR"/*
625         fi
626         exit $?
627 fi
628
629 # -D ensures that the listed config file base dirs exist
630 if [ "$cmd" = D ]; then
631         config_mkdirs "$@"
632         exit $?
633 fi
634
635 # -l lists our resolv files, optionally for a specific interface
636 if [ "$cmd" = l -o "$cmd" = i ]; then
637         list_resolv "$cmd" "$args"
638         exit $?
639 fi
640
641 # Restart a service or echo the command to restart a service
642 if [ "$cmd" = r -o "$cmd" = R ]; then
643         detect_init || exit 1
644         if [ "$cmd" = r ]; then
645                 set -- $args
646                 eval $RESTARTCMD
647         else
648                 echo "$RESTARTCMD"
649         fi
650         exit $?
651 fi
652
653 # Not normally needed, but subscribers should be able to run independently
654 if [ "$cmd" = v -o -n "$VFLAG" ]; then
655         make_vars "$iface"
656         exit $?
657 fi
658
659 # Test that we have valid options
660 if [ "$cmd" = a -o "$cmd" = d ]; then
661         if [ -z "$iface" ]; then
662                 usage "Interface not specified"
663         fi
664 elif [ "$cmd" != u ]; then
665         [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd"
666         usage
667 fi
668
669 if [ "$cmd" = a ]; then
670         for x in '/' \\ ' ' '*'; do
671                 case "$iface" in
672                 *[$x]*) error_exit "$x not allowed in interface name";;
673                 esac
674         done
675         for x in '.' '-' '~'; do
676                 case "$iface" in
677                 [$x]*) error_exit \
678                         "$x not allowed at start of interface name";;
679                 esac
680         done
681         [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin"
682 fi
683
684 if [ ! -d "$VARDIR" ]; then
685         if [ -L "$VARDIR" ]; then
686                 dir="$(readlink "$VARDIR")"
687                 # link maybe relative
688                 cd "${VARDIR%/*}"
689                 if ! mkdir -m 0755 -p "$dir"; then
690                         error_exit "Failed to create needed" \
691                                 "directory $dir"
692                 fi
693         else
694                 if ! mkdir -m 0755 -p "$VARDIR"; then
695                         error_exit "Failed to create needed" \
696                                 "directory $VARDIR"
697                 fi
698         fi
699 fi
700
701 if [ ! -d "$IFACEDIR" ]; then
702         mkdir -m 0755 -p "$IFACEDIR" || \
703                 error_exit "Failed to create needed directory $IFACEDIR"
704         if [ "$cmd" = d ]; then
705                 # Provide the same error messages as below
706                 if ! ${force}; then
707                         cd "$IFACEDIR"
708                         for i in $args; do
709                                 warn "No resolv.conf for interface $i"
710                         done
711                 fi
712                 ${force}
713                 exit $?
714         fi
715 fi
716
717 # An interface was added, changed, deleted or a general update was called.
718 # Due to exclusivity we need to ensure that this is an atomic operation.
719 # Our subscribers *may* need this as well if the init system is sub par.
720 # As such we spinlock at this point as best we can.
721 # We don't use flock(1) because it's not widely available and normally resides
722 # in /usr which we do our very best to operate without.
723 [ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
724 : ${lock_timeout:=10}
725 while true; do
726         if mkdir "$LOCKDIR" 2>/dev/null; then
727                 trap 'rm -rf "$LOCKDIR";' EXIT
728                 trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
729                 echo $$ >"$LOCKDIR/pid"
730                 break
731         fi
732         pid=$(cat "$LOCKDIR/pid")
733         if ! kill -0 "$pid"; then
734                 warn "clearing stale lock pid $pid"
735                 rm -rf "$LOCKDIR"
736                 continue
737         fi
738         lock_timeout=$(($lock_timeout - 1))
739         if [ "$lock_timeout" -le 0 ]; then
740                 error_exit "timed out waiting for lock from pid $pid"
741         fi
742         sleep 1
743 done
744
745 case "$cmd" in
746 a)
747         # Read resolv.conf from stdin
748         resolv="$(cat)"
749         changed=false
750         changedfile=false
751         # If what we are given matches what we have, then do nothing
752         if [ -e "$IFACEDIR/$iface" ]; then
753                 if [ "$(echo "$resolv")" != \
754                         "$(cat "$IFACEDIR/$iface")" ]
755                 then
756                         changed=true
757                         changedfile=true
758                 fi
759         else
760                 changed=true
761                 changedfile=true
762         fi
763
764         # Set metric and private before creating the interface resolv.conf file
765         # to ensure that it will have the correct flags
766         [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
767         oldmetric="$METRICDIR/"*" $iface"
768         newmetric=
769         if [ -n "$IF_METRIC" ]; then
770                 # Pad metric to 6 characters, so 5 is less than 10
771                 while [ ${#IF_METRIC} -le 6 ]; do
772                         IF_METRIC="0$IF_METRIC"
773                 done
774                 newmetric="$METRICDIR/$IF_METRIC $iface"
775         fi
776         rm -f "$METRICDIR/"*" $iface"
777         [ "$oldmetric" != "$newmetric" -a \
778             "$oldmetric" != "$METRICDIR/* $iface" ] &&
779                 changed=true
780         [ -n "$newmetric" ] && echo " " >"$newmetric"
781
782         case "$IF_PRIVATE" in
783         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
784                 if [ ! -d "$PRIVATEDIR" ]; then
785                         [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
786                         mkdir "$PRIVATEDIR"
787                 fi
788                 [ -e "$PRIVATEDIR/$iface" ] || changed=true
789                 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
790                 ;;
791         *)
792                 if [ -e "$PRIVATEDIR/$iface" ]; then
793                         rm -f "$PRIVATEDIR/$iface"
794                         changed=true
795                 fi
796                 ;;
797         esac
798
799         oldexcl=
800         for x in "$EXCLUSIVEDIR/"*" $iface"; do
801                 if [ -f "$x" ]; then
802                         oldexcl="$x"
803                         break
804                 fi
805         done
806         case "$IF_EXCLUSIVE" in
807         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
808                 if [ ! -d "$EXCLUSIVEDIR" ]; then
809                         [ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
810                         mkdir "$EXCLUSIVEDIR"
811                 fi
812                 cd "$EXCLUSIVEDIR"
813                 for x in *; do
814                         [ -f "$x" ] && break
815                 done
816                 if [ "${x#* }" != "$iface" ]; then
817                         if [ "$x" = "${x% *}" ]; then
818                                 x=10000000
819                         else
820                                 x="${x% *}"
821                         fi
822                         if [ "$x" = "0000000" ]; then
823                                 warn "exclusive underflow"
824                         else
825                                 x=$(($x - 1))
826                         fi
827                         if [ -d "$EXCLUSIVEDIR" ]; then
828                                 echo " " >"$EXCLUSIVEDIR/$x $iface"
829                         fi
830                         changed=true
831                 fi
832                 ;;
833         *)
834                 if [ -f "$oldexcl" ]; then
835                         rm -f "$oldexcl"
836                         changed=true
837                 fi
838                 ;;
839         esac
840
841         if $changedfile; then
842                 printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $?
843         elif ! $changed; then
844                 exit 0
845         fi
846         unset changed changedfile oldmetric newmetric x oldexcl
847         ;;
848
849 d)
850         # Delete any existing information about the interface
851         cd "$IFACEDIR"
852         changed=false
853         for i in $args; do
854                 if [ -e "$i" ]; then
855                         changed=true
856                 elif ! ${force}; then
857                         warn "No resolv.conf for interface $i"
858                 fi
859                 rm -f "$i" "$METRICDIR/"*" $i" \
860                         "$PRIVATEDIR/$i" \
861                         "$EXCLUSIVEDIR/"*" $i" || exit $?
862         done
863         if ! ${changed}; then
864                 # Set the return code based on the forced flag
865                 ${force}
866                 exit $?
867         fi
868         unset changed i
869         ;;
870 esac
871
872 case "${resolvconf:-YES}" in
873 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
874 *) exit 0;;
875 esac
876
877 # Try and detect a suitable init system for our scripts
878 detect_init
879 export RESTARTCMD RCDIR _NOINIT_WARNED
880
881 eval "$(make_vars)"
882 export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
883 : ${list_resolv:=list_resolv -l}
884 retval=0
885
886 # Run scripts in the same directory resolvconf is run from
887 # in case any scripts accidently dump files in the wrong place.
888 cd "$_PWD"
889 for script in "$LIBEXECDIR"/*; do
890         if [ -f "$script" ]; then
891                 eval script_enabled="\$${script##*/}"
892                 case "${script_enabled:-YES}" in
893                 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
894                 *) continue;;
895                 esac
896                 if [ -x "$script" ]; then
897                         "$script" "$cmd" "$iface"
898                 else
899                         (set -- "$cmd" "$iface"; . "$script")
900                 fi
901                 retval=$(($retval + $?))
902         fi
903 done
904 exit $retval