1#!@BASH_SHELL@
2
3#
4# Script to manage a Samba file-sharing service component.
5# Unline NFS, this should be placed at the top level of a service
6# because it will try to gather information necessary to run the
7# smbd/nmbd daemons at run-time from the service structure.
8#
9# Copyright (C) 1997-2003 Sistina Software, Inc.  All rights reserved.
10# Copyright (C) 2004-2011 Red Hat, Inc.  All rights reserved.
11#
12# This program is free software; you can redistribute it and/or
13# modify it under the terms of the GNU General Public License
14# as published by the Free Software Foundation; either version 2
15# of the License, or (at your option) any later version.
16#
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with this program; if not, write to the Free Software
24# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25#
26# Author(s):
27#  Lon Hohberger (lhh at redhat.com)
28#  Tim Burke (tburke at redhat.com)
29#
30
31LC_ALL=C
32LANG=C
33PATH=/bin:/sbin:/usr/bin:/usr/sbin
34export LC_ALL LANG PATH
35
36#
37# Definitions!
38#
39declare SAMBA_CONFIG_DIR=/etc/samba
40declare SMBD_COMMAND=/usr/sbin/smbd
41declare NMBD_COMMAND=/usr/sbin/nmbd
42declare KILLALL_COMMAND=/usr/bin/killall
43declare SAMBA_PID_DIR=/var/run/samba
44declare SAMBA_LOCK_DIR=/var/cache/samba
45
46#
47# gross globals
48#
49declare -a ipkeys
50declare -a fskeys
51
52# Don't change please :)
53_FAIL=255
54
55. $(dirname $0)/ocf-shellfuncs
56
57meta_data()
58{
59    cat <<EOT
60<?xml version="1.0"?>
61<resource-agent version="rgmanager 2.0" name="smb">
62    <version>1.0</version>
63
64    <longdesc lang="en">
65    	Dynamic smbd/nmbd resource agent
66    </longdesc>
67    <shortdesc lang="en">
68    	Dynamic smbd/nmbd resource agent
69    </shortdesc>
70
71    <parameters>
72        <parameter name="name" unique="1" primary="1">
73            <longdesc lang="en">
74                Samba Symbolic Name.  This name will
75		correspond to /etc/samba/smb.conf.NAME
76            </longdesc>
77            <shortdesc lang="en">
78                Samba Name
79            </shortdesc>
80	    <content type="string"/>
81        </parameter>
82
83        <parameter name="workgroup">
84            <longdesc lang="en">
85	    	Workgroup name
86            </longdesc>
87            <shortdesc lang="en">
88	    	Workgroup name
89            </shortdesc>
90	    <content type="string" default="LINUXCLUSTER"/>
91        </parameter>
92
93        <parameter name="service_name" inherit="service%name">
94            <longdesc lang="en">
95	    	Inherit the service name.  We need to know
96		the service name in order to determine file
97		systems and IPs for this smb service.
98            </longdesc>
99            <shortdesc lang="en">
100	    	Inherit the service name.
101            </shortdesc>
102	    <content type="string"/>
103        </parameter>
104    </parameters>
105
106    <actions>
107        <action name="start" timeout="0"/>
108        <action name="stop" timeout="0"/>
109
110	<!-- This is just a wrapper for LSB init scripts, so monitor
111	     and status can't have a timeout, nor do they do any extra
112	     work regardless of the depth -->
113        <action name="status" interval="30s" timeout="0"/>
114        <action name="monitor" interval="30s" timeout="0"/>
115
116        <action name="meta-data" timeout="0"/>
117        <action name="validate-all" timeout="0"/>
118    </actions>
119</resource-agent>
120EOT
121}
122
123
124#
125# Usage: ccs_get key
126#
127ccs_get()
128{
129	declare outp
130	declare key
131
132	[ -n "$1" ] || return $_FAIL
133
134	key="$*"
135
136	outp=$(ccs_tool query "$key" 2>&1)
137	if [ $? -ne 0 ]; then
138		if [ "$outp" = "${outp/No data available/}" ] || [ "$outp" = "${outp/Operation not permitted/}" ]; then
139			ocf_log err "$outp ($key)"
140			return $_FAIL
141		fi
142
143		# no real error, just no data available
144		return 0
145	fi
146
147	echo $outp
148
149	return 0
150}
151
152
153#
154# Build a list of service IP keys; traverse refs if necessary
155#
156get_service_ip_keys()
157{
158	declare svc=$1
159	declare -i x y=0
160	declare outp
161	declare key
162
163	#
164	# Find service-local IP keys
165	#
166	x=1
167	while : ; do
168		key="/cluster/rm/service[@name=\"$svc\"]/ip[$x]"
169
170		#
171		# Try direct method
172		#
173		outp=$(ccs_get "$key/@address")
174		if [ $? -ne 0 ]; then
175			return 1
176		fi
177
178		#
179		# Try by reference
180		#
181		if [ -z "$outp" ]; then
182			outp=$(ccs_get "$key/@ref")
183			if [ $? -ne 0 ]; then
184				return 1
185			fi
186			key="/cluster/rm/resources/ip[@address=\"$outp\"]"
187		fi
188
189		if [ -z "$outp" ]; then
190			break
191		fi
192
193		#ocf_log debug "IP $outp found @ $key"
194
195		ipkeys[$y]="$key"
196
197		((y++))
198		((x++))
199	done
200
201	ocf_log debug "$y IP addresses found for $svc/$OCF_RESKEY_name"
202
203	return 0
204}
205
206
207#
208# Build a list of service fs keys, traverse refs if necessary
209#
210get_service_fs_keys()
211{
212	declare svc=$1
213	declare -i x y=0
214	declare outp
215	declare key
216
217	#
218	# Find service-local IP keys
219	#
220	x=1
221	while : ; do
222		key="/cluster/rm/service[@name=\"$svc\"]/fs[$x]"
223
224		#
225		# Try direct method
226		#
227		outp=$(ccs_get "$key/@name")
228		if [ $? -ne 0 ]; then
229			return 1
230		fi
231
232		#
233		# Try by reference
234		#
235		if [ -z "$outp" ]; then
236			outp=$(ccs_get "$key/@ref")
237			if [ $? -ne 0 ]; then
238				return 1
239			fi
240			key="/cluster/rm/resources/fs[@name=\"$outp\"]"
241		fi
242
243		if [ -z "$outp" ]; then
244			break
245		fi
246
247		#ocf_log debug "filesystem $outp found @ $key"
248
249		fskeys[$y]="$key"
250
251		((y++))
252		((x++))
253	done
254
255	ocf_log debug "$y filesystems found for $svc/$OCF_RESKEY_name"
256
257	return 0
258}
259
260
261build_ip_list()
262{
263	declare ipaddrs ipaddr
264	declare -i x=0
265
266	while [ -n "${ipkeys[$x]}" ]; do
267		ipaddr=$(ccs_get "${ipkeys[$x]}/@address")
268		if [ -z "$ipaddr" ]; then
269			break
270		fi
271
272		ipaddrs="$ipaddrs $ipaddr"
273
274		((x++))
275	done
276
277	echo $ipaddrs
278}
279
280
281add_sha1()
282{
283	declare sha1line="# rgmanager-sha1 $(sha1sum "$1")"
284	echo $sha1line >> "$1"
285}
286
287
288verify_sha1()
289{
290	declare tmpfile="$(mktemp /tmp/smb-$OCF_RESKEY_name.tmp.XXXXXX)"
291	declare current exp
292
293	exp=$(grep "^# rgmanager-sha1.*$1" "$1" | head -1)
294	if [ -z "$exp" ]; then
295		# No sha1 line.  We're done.
296		ocf_log debug "No SHA1 info in $1"
297		return 1
298	fi
299
300	#
301	# Find expected sha1 and expected file name
302	#
303	exp=${exp/*sha1 /}
304	exp=${exp/ */}
305
306	grep -v "^# rgmanager-sha1" "$1" > "$tmpfile"
307	current=$(sha1sum "$tmpfile")
308	current=${current/ */}
309
310	rm -f "$tmpfile"
311
312	if [ "$current" = "$exp" ]; then
313		ocf_log debug "SHA1 sum matches for $1"
314		return 0
315	fi
316	ocf_log debug "SHA1 sum does not match for $1"
317	return 1
318}
319
320
321add_fs_entries()
322{
323	declare conf="$1"
324	declare sharename
325	declare sharepath key
326
327	declare -i x=0
328
329	while [ -n "${fskeys[$x]}" ]; do
330		key="${fskeys[$x]}/@name"
331
332		sharename=$(ccs_get "$key")
333		if [ -z "$sharename" ]; then
334			break
335		fi
336
337		key="${fskeys[$x]}/@mountpoint"
338		sharepath=$(ccs_get "$key")
339		if [ -z "$sharepath" ]; then
340			break
341		fi
342
343		cat >> "$conf" <<EODEV
344[$sharename]
345	comment = Auto-generated $sharename share
346	# Hide the secret cluster files
347	veto files = /.clumanager/.rgmanager/
348	browsable = yes
349	writable = no
350	public = yes
351	path = $sharepath
352
353EODEV
354
355		((x++))
356	done
357}
358
359
360#
361# Generate the samba configuration if neede for this service.
362#
363gen_smb_conf()
364{
365	declare conf="$1"
366	declare lvl="debug"
367
368	if [ -f "$conf" ]; then
369		verify_sha1 "$conf"
370		if [ $? -ne 0 ]; then
371			ocf_log debug "Config file changed; skipping"
372			return 0
373		fi
374	else
375		lvl="info"
376	fi
377
378	ocf_log $lvl "Creating $conf"
379
380	get_service_ip_keys "$OCF_RESKEY_service_name"
381	get_service_fs_keys "$OCF_RESKEY_service_name"
382
383	cat > "$conf" <<EOT
384#
385# "$conf"
386#
387# This template configuration wass automatically generated, and will
388# be automatically regenerated if removed.  Please modify this file to
389# speficy subdirectories and/or client access permissions.
390#
391# Once this file has been altered, automatic re-generation will stop.
392# Remember to copy this file to all other cluster members after making
393# changes, or your SMB service will not operate correctly.
394#
395# From a cluster perspective, the key fields are:
396#     lock directory - must be unique per samba service.
397#     bind interfaces only - must be present set to yes.
398#     interfaces - must be set to service floating IP address.
399#     path - must be the service mountpoint or subdirectory thereof.
400#
401
402[global]
403        workgroup = $OCF_RESKEY_workgroup
404        pid directory = /var/run/samba/$OCF_RESKEY_name
405        lock directory = /var/cache/samba/$OCF_RESKEY_name
406	log file = /var/log/samba/%m.log
407	#private dir = /var/
408	encrypt passwords = yes
409	bind interfaces only = yes
410	netbios name = ${OCF_RESKEY_name/ /_}
411
412	#
413	# Interfaces are based on ip resources at the top level of
414	# "$OCF_RESKEY_service_name"; IPv6 addresses may or may not
415	# work correctly.
416	#
417	interfaces = $(build_ip_list)
418
419#
420# Shares based on fs resources at the top level of "$OCF_RESKEY_service_name"
421#
422EOT
423	add_fs_entries "$conf"
424	add_sha1 "$conf"
425
426	return 0
427}
428
429
430#
431# Kill off the specified PID
432# (from clumanager 1.0.x/1.2.x)
433#
434# Killing off the samba daemons was miserable to implement, merely
435# because killall doesn't distinguish by program commandline.
436# Consequently I had to implement these routines to selectively pick 'em off.
437#
438# Kills of either the {smbd|nmbd} which is running and was started with
439# the specified argument.  Can't use `killall` to do this because it
440# doesn't allow you to distinguish which process to kill based on any
441# of the program arguments.
442#
443# This routine is also called on "status" checks.  In this case it doesn't
444# actually kill anything.
445#
446# Parameters:
447#       daemonName - daemon name, can be either smbd or nmbd
448#       command - [stop|start|status]
449#       arg     - argument passed to daemon.  In this case its not the
450#                 full set of program args, rather its really just the
451#                 samba config file.
452#
453# Returns: 0 - success (or the daemon isn't currently running)
454#          1 - failure
455#
456kill_daemon_by_arg()
457{
458    	declare daemonName=$1
459	declare action=$2
460	declare arg=$3
461	# Create a unique temporary file to stash off intermediate results
462	declare tmpfile_str=/tmp/sambapids.XXXXXX
463	declare tmpfile
464	declare ret
465
466	tmpfile=$(mktemp $tmpfile_str); ret_val=$?
467
468	if [ -z "$tmpfile" ]; then
469		ocf_log err "kill_daemon_by_arg: Can't create tmp file"
470		return $_FAIL
471	fi
472
473	# Mumble, need to strip off the /etc/samba portion, otherwise the
474	# grep pattern matching will fail.
475	declare confFile="$(basename $arg)"
476
477	# First generate a list of candidate pids.
478	pidof $daemonName > $tmpfile
479	if [ $? -ne 0 ]; then
480		ocf_log debug "kill_daemon_by_arg: no pids for $daemonName"
481		rm -f $tmpfile
482		case "$action" in
483		'stop')
484			return 0
485		;;
486		'status')
487			return $_FAIL
488		;;
489		esac
490		return 0
491	fi
492
493	# If you don't find any matching daemons for a "stop" operation, thats
494	# considered success; whereas for "status" inquiries its a failure.
495	case "$action" in
496	'stop')
497		ret=0
498	;;
499	'status')
500		ret=$_FAIL
501	;;
502	esac
503	#
504	# At this point tmpfile contains a set of pids for the corresponding
505	# {smbd|nmbd}.  Now look though this candidate set of pids and compare
506	# the program arguments (samba config file name).  This distinguishes
507	# which ones should be killed off.
508	#
509	declare daemonPid=""
510	for daemonPid in $(cat $tmpfile); do
511		declare commandLine=$(cat /proc/$daemonPid/cmdline)
512		declare confBase="$(basename $commandLine)"
513		if [ "$confBase" = "$confFile" ]; then
514			case "$action" in
515			'status')
516				rm -f $tmpfile
517				return 0
518			;;
519			esac
520			kill_daemon_pid $daemonPid
521			if [ $? -ne 0 ]; then
522				ret=$_FAIL
523				ocf_log err \
524				"kill_daemon_by_arg: kill_daemon_pid $daemonPid failed"
525			else
526				ocf_log debug \
527				 "kill_daemon_by_arg: kill_daemon_pid $daemonPid success"
528			fi
529		fi
530	done
531	rm -f $tmpfile
532	return $ret
533}
534
535
536#
537# Kill off the specified PID
538# (from clumanager 1.0.x/1.2.x)
539#
540kill_daemon_pid()
541{
542	declare pid=$1
543	declare retval=0
544
545
546	kill -TERM $pid
547	if [ $? -eq 0 ]; then
548		ocf_log debug "Samba: successfully killed $pid"
549	else
550		ocf_log debug "Samba: failed to kill $pid"
551		retval=$_FAIL
552	fi
553	return $retval
554}
555
556
557share_start_stop()
558{
559	declare command=$1
560	declare conf="$SAMBA_CONFIG_DIR/smb.conf.$OCF_RESKEY_name"
561	declare smbd_command
562	declare nmbd_command
563	declare netbios_name
564
565	#
566	# Specify daemon options
567	# -D = spawn off as separate daemon
568	# -s = the following arg specifies the config file
569	#
570	declare smbd_options="-D -s"
571	declare nmbd_options="-D -s"
572
573	if [ "$command" = "start" ]; then
574		gen_smb_conf "$conf"
575	else
576		if ! [ -f "$conf" ]; then
577			ocf_log warn "\"$conf\" missing during $command"
578		fi
579	fi
580
581	#
582	# On clusters with multiple samba shares, we need to ensure (as much
583	# as possible) that each service is advertised as a separate netbios
584	# name.
585	#
586	# Generally, the admin sets this in smb.conf.NAME - but since
587	# it is not required, we need another option.  Consequently, we use
588	# smb instance name (which must be unique)
589	#
590	if [ -f "$conf" ]; then
591		grep -qe "^\([[:space:]]\+n\|n\)etbios[[:space:]]\+name[[:space:]]*=[[:space:]]*[[:alnum:]]\+" "$conf"
592		if [ $? -ne 0 ]; then
593
594			netbios_name=$OCF_RESKEY_name
595
596			ocf_log notice "Using $netbios_name as NetBIOS name (service $OCF_RESKEY_service_name)"
597			nmbd_options=" -n $netbios_name $nmbd_options"
598		fi
599	fi
600
601	case $command in
602	start)
603		ocf_log info "Starting Samba instance \"$OCF_RESKEY_name\""
604		mkdir -p "$SAMBA_PID_DIR/$OCF_RESKEY_name"
605		mkdir -p "$SAMBA_LOCK_DIR/$OCF_RESKEY_name"
606
607		[ -f "$SMBD_COMMAND" ] || exit $OCF_ERR_INSTALLED
608		[ -f "$NMBD_COMMAND" ] || exit $OCF_ERR_INSTALLED
609
610		# Kick off the per-service smbd
611		$SMBD_COMMAND $smbd_options "$conf"
612		ret_val=$?
613		if [ $ret_val -ne 0 ]; then
614			ocf_log err "Samba service failed: $SMBD_COMMAND $smbd_options \"$conf\""
615			return $_FAIL
616		fi
617		ocf_log debug "Samba service succeeded: $SMBD_COMMAND $smbd_options \"$conf\""
618
619		# Kick off the per-service nmbd
620		$NMBD_COMMAND $nmbd_options "$conf"
621		ret_val=$?
622		if [ $ret_val -ne 0 ]; then
623			ocf_log err "Samba service failed: $NMBD_COMMAND $nmbd_options \"$conf\""
624			return $_FAIL
625		fi
626		ocf_log debug "Samba service succeeded: $NMBD_COMMAND $nmbd_options \"$conf\""
627	;;
628	stop)
629		ocf_log info "Stopping Samba instance \"$OCF_RESKEY_name\""
630
631       		kill_daemon_by_arg "nmbd" $command "$conf"
632       		kill_daemon_by_arg "smbd" $command "$conf"
633       		if [ "$SAMBA_PID_DIR/$OCF_RESKEY_name" != "/" ]; then
634       			pushd "$SAMBA_PID_DIR" &> /dev/null
635       			rm -rf "$OCF_RESKEY_name"
636	       		popd &> /dev/null
637		fi
638		if [ "$SAMBA_LOCK_DIR/$OCF_RESKEY_name" != "/" ]; then
639     			pushd "$SAMBA_LOCK_DIR" &> /dev/null
640			rm -rf "$OCF_RESKEY_name"
641			popd &> /dev/null
642		fi
643	;;
644	status)
645		ocf_log debug "Checking Samba instance \"$OCF_RESKEY_name\""
646		kill_daemon_by_arg "nmbd" $command "$conf"
647		if [ $? -ne 0 ]; then
648			ocf_log err \
649			"share_start_stop: nmbd for service $svc_name died!"
650			return $_FAIL
651		fi
652		kill_daemon_by_arg "smbd" $command "$conf"
653		if [ $? -ne 0 ]; then
654			ocf_log err \
655		"share_start_stop: nmbd for service $svc_name died!"
656			return $_FAIL
657		fi
658	;;
659	esac
660}
661
662
663verify_all()
664{
665	[ -z "$OCF_RESKEY_workgroup" ] && export OCF_RESKEY_workgroup="LINUXCLUSTER"
666	[ -n "${OCF_RESKEY_name}" ] || exit $OCF_ERR_ARGS      # Invalid Argument
667	if [ -z "${OCF_RESKEY_service_name}" ]; then
668		ocf_log ERR "Samba service ${OCF_RESKEY_name} is not the child of a service"
669		exit $OCF_ERR_ARGS
670	fi
671}
672
673case $1 in
674	meta-data)
675		meta_data
676		exit 0
677	;;
678	start|stop)
679		verify_all
680		share_start_stop $1
681		exit $?
682	;;
683	status|monitor)
684		verify_all
685		share_start_stop status
686		exit $?
687	;;
688	validate-all)
689		verify_all
690		echo "Yer radio's workin', driver!"
691		exit 0
692	;;
693	*)
694		echo "usage: $0 {start|stop|status|monitor|meta-data|validate-all}"
695		exit $OCF_ERR_UNIMPLEMENTED
696	;;
697esac
698
699