diff --git a/.cvsignore b/.cvsignore deleted file mode 100644 index f26f7e8..0000000 --- a/.cvsignore +++ /dev/null @@ -1,24 +0,0 @@ -Makefile -Makefile.am -Makefile.in -aclocal.m4 -autom4te.cache -config.cache -config.guess -config.h -config.h.in -config.log -config.status -config.sub -configure -configure.in -depcomp -install-sh -libtool -ltconfig -ltmain.sh -missing -mkinstalldirs -stamp-h* -*.pot -Makefile.am.common diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aab5d58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# autotools +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +config.guess +config.log +config.status +config.sub +configure +install-sh +missing +# devtools +.dep +*.ami +/Makefile.am +/Makefile.am.common +configure.in +doc/autodocs/*.html diff --git a/VERSION b/VERSION index 4a36342..cb2b00e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0 +3.0.1 diff --git a/agents/.cvsignore b/agents/.cvsignore deleted file mode 100644 index 282522d..0000000 --- a/agents/.cvsignore +++ /dev/null @@ -1,2 +0,0 @@ -Makefile -Makefile.in diff --git a/agents/Makefile.am b/agents/Makefile.am deleted file mode 100644 index dec8b7c..0000000 --- a/agents/Makefile.am +++ /dev/null @@ -1,5 +0,0 @@ -# -# Makefile.am for nfs-client/agents -# -# $Id$ -# diff --git a/doc/.cvsignore b/doc/.cvsignore deleted file mode 100644 index 282522d..0000000 --- a/doc/.cvsignore +++ /dev/null @@ -1,2 +0,0 @@ -Makefile -Makefile.in diff --git a/doc/autodocs/.cvsignore b/doc/autodocs/.cvsignore deleted file mode 100644 index 2f083ef..0000000 --- a/doc/autodocs/.cvsignore +++ /dev/null @@ -1,3 +0,0 @@ -Makefile -Makefile.in -*.html diff --git a/doc/autodocs/Makefile.am b/doc/autodocs/Makefile.am index 2a6f678..e883f62 100644 --- a/doc/autodocs/Makefile.am +++ b/doc/autodocs/Makefile.am @@ -1,3 +1,5 @@ # Makefile.am for YCP module .../doc/autodocs +AUTODOCS_YCP = $(wildcard $(top_srcdir)/src/*/*.ycp $(top_srcdir)/src/*/*/*.ycp) +AUTODOCS_STRIP = $(top_srcdir)/src include $(top_srcdir)/autodocs-ycp.ami diff --git a/package/.cvsignore b/package/.cvsignore deleted file mode 100644 index 65d0b1a..0000000 --- a/package/.cvsignore +++ /dev/null @@ -1,2 +0,0 @@ -*.spec -*.bz2 diff --git a/package/yast2-nfs-client.changes b/package/yast2-nfs-client.changes index a60c473..b997b07 100644 --- a/package/yast2-nfs-client.changes +++ b/package/yast2-nfs-client.changes @@ -1,3 +1,22 @@ +------------------------------------------------------------------- +Fri Sep 13 12:18:03 UTC 2013 - mvidner@suse.com + +- merged changes from SLE11-SP3 + - refactored the last change following a late review + (FATE#312242, bnc#820989) + - AutoYaST: fix to allow NFS4 options (FATE#312242, bnc#457981) + - added option for enabling NFS4 GSS (FATE#312242, bnc#681190) + - Added minorversion=1 for pNFS, NFS4.1 (FATE#312259). + - enable compiling, testing, packaging from the WC (bnc#790490) +- merged changelogs from SLE-SP3 for changes + that already are in this branch + - fix enablement of NFSv4 in autoyast (bnc#684859) + - allow to set version of nfs on command line and also fix passing + default value (bnc#681275) + - Consistent setting of widget focus esp. when embedded in partitioner + (bnc#435992) +- 3.0.1 + ------------------------------------------------------------------- Wed Jul 31 08:33:54 UTC 2013 - yast-devel@opensuse.org diff --git a/src/.cvsignore b/src/.cvsignore deleted file mode 100644 index 31486da..0000000 --- a/src/.cvsignore +++ /dev/null @@ -1,4 +0,0 @@ -Makefile -Makefile.in -.dep -*.ybc diff --git a/src/Makefile.am b/src/Makefile.am index 406c6e7..2b0e8df 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,7 @@ # Sources for nfs-client module_DATA = \ + modules/NfsOptions.rb \ modules/Nfs.rb client_DATA = \ @@ -24,4 +25,4 @@ desktop_DATA = \ EXTRA_DIST = $(module_DATA) $(client_DATA) $(ynclude_DATA) $(schemafiles_DATA) $(desktop_DATA) -include $(top_srcdir)/Makefile.am.common \ No newline at end of file +include $(top_srcdir)/Makefile.am.common diff --git a/src/autoyast-rnc/nfs.rnc b/src/autoyast-rnc/nfs.rnc index 1536cac..50ab417 100644 --- a/src/autoyast-rnc/nfs.rnc +++ b/src/autoyast-rnc/nfs.rnc @@ -1,14 +1,62 @@ -nfs = +# It is complicated. + +nfs_entry_content = ( + element server_path { text } + & element mount_point { text } + & element vfstype {text}? + & element nfs_options { text } +) + +# SLE11-SP2 and earlier, openSUSE 11.2 and earlier: +# A list of entries +# (This definition is unused below but provided for historical context) +nfs_sle11_sp2 = element nfs { + LIST, + element nfs_entry { + nfs_entry_content + }* + } + +# openSUSE 11.3-12.3 (since yast2-nfs-client-2.19.1, openSUSE-11.3, 2010): +# Switched to a map so that we can have global options + +nfs_global_options_content = ( element enable_nfs4 { BOOLEAN }? & - element idmapd_domain { text }? & + element enable_nfs_gss { BOOLEAN }? & + element idmapd_domain { text }? +) + +nfs_os113_123 = + element nfs { + nfs_global_options_content, element nfs_entries { LIST, element nfs_entry { - element server_path { text } - & element mount_point { text } - & element vfstype {text}? - & element nfs_options { text } + nfs_entry_content }* } + +# Now we want to port the options to SLE11-SP3. +# But the AutoYaST core does not allow switching a list to a map! +# bnc#820989 + +# SLE11-SP3: +# a list, like in SLE11-SP2, but +# with an optional first entry which carries the global options +nfs_sle11_sp3 = + element nfs { + LIST, + element nfs_entry { + nfs_global_options_content + }? , + element nfs_entry { + nfs_entry_content + }* } + +# openSUSE-13.1: +# Merge: allow all formats +# Well, on this layer only. The AY core still allows a map only. +# Needs to be fixed there. +nfs = nfs_os113_123 | nfs_sle11_sp3 diff --git a/src/clients/nfs.rb b/src/clients/nfs.rb index 8b1dead..9636ae3 100644 --- a/src/clients/nfs.rb +++ b/src/clients/nfs.rb @@ -26,6 +26,7 @@ def main Builtins.y2milestone("NFS module started") Yast.import "Nfs" + Yast.import "NfsOptions" Yast.import "Progress" Yast.import "Report" Yast.import "String" @@ -207,7 +208,7 @@ def NfsAddHandler(options) Ops.set(options, "mntops", "defaults") end - options_error = check_options(Ops.get_string(options, "mntops", "")) + options_error = NfsOptions.validate(Ops.get_string(options, "mntops", "")) if Ops.greater_than(Builtins.size(options_error), 0) Report.Error(options_error) return false @@ -317,7 +318,7 @@ def NfsEditHandler(options) return false end - options_error = check_options(Ops.get_string(entry, "mntops", "")) + options_error = NfsOptions.validate(Ops.get_string(entry, "mntops", "")) if Ops.greater_than(Builtins.size(options_error), 0) Report.Error(options_error) return false diff --git a/src/include/nfs/routines.rb b/src/include/nfs/routines.rb index 92abbea..694680d 100644 --- a/src/include/nfs/routines.rb +++ b/src/include/nfs/routines.rb @@ -162,141 +162,6 @@ def CheckPath(name) false end - # Checks the nfs options for /etc/fstab: - # nonempty, comma separated list of foo,nofoo,bar=baz (see nfs(5)) - # @param [String] options options - # @return a translated string with error message, emtpy string if ok - def check_options(options) - # To translators: error popup - if Builtins.size(options) == 0 - return _("Empty option strings are not allowed.") - end - return "" if options == "defaults" - - - option_list = Builtins.splitstring(options, ",") - - #the options must be easy to sync with mount.c and nfsmount.c - - # these can be negated by "no" - non_value = [ - "bg", - "fg", - "soft", - "hard", - "intr", - "posix", - "cto", - "ac", - "acl", - "lock", - "tcp", - "udp", - "rdirplus", - "sharecache", - "resvport", - "fsc", - # these are common for all fs types - "atime", - "auto", - "dev", - "exec", - "group", - "owner", - "suid", - "user", - "users", - "sub", - "mand", - "loop", - "diratime", - "relatime", - "quota" - ] - # these cannot be negated - # they are not nfs specific BTW - non_value1 = [ - "defaults", - "async", - "sync", - "dirsync", - "ro", - "rw", - "remount", - "bind", - "rbind", - "_netdev", - "nofail", - "rdma", - "quiet", - "loud", - "usrquota", - "grpquota" - ] - with_value = [ - "rsize", - "wsize", - "timeo", - "retrans", - "acregmin", - "acregmax", - "acdirmin", - "acdirmax", - "actimeo", - "retry", - "namlen", - "port", - "proto", - "clientaddr", - "mountport", - "mountproto", - "mounthost", - "mountprog", - "mountvers", - "nfsprog", - "nfsvers", - "vers", - "sec", - "comment", - "lookupcache", - "local_lock" - ] - i = 0 - current_option = "" - - # first fiter out non value options and its nooptions forms (see nfs(5)) - option_list = Builtins.filter(option_list) do |e| - !Builtins.contains(non_value, e) - end - non_value = Builtins.maplist(non_value) { |e| Builtins.sformat("no%1", e) } - option_list = Builtins.filter(option_list) do |e| - !Builtins.contains(non_value, e) - end - option_list = Builtins.filter(option_list) do |e| - !Builtins.contains(non_value1, e) - end - - while Ops.less_than(i, Builtins.size(option_list)) - opt = Ops.get(option_list, i, "") - value = Builtins.splitstring(opt, "=") - v0 = Ops.get(value, 0, "") - v1 = Ops.get(value, 1, "") - # FIXME: this also triggers for "intr=bogus" - # To translators: error popup - if !Builtins.contains(with_value, v0) - return Builtins.sformat(_("Unknown option: %1"), v0) - end - # To translators: error popup - if Builtins.size(value) != 2 - return Builtins.sformat(_("Invalid option: %1"), opt) - end - # To translators: error popup - return Builtins.sformat(_("Empty value for option: %1"), v0) if v1 == "" - i = Ops.add(i, 1) - end - - "" - end # Strips a superfluous slash off the end of a pathname. # @param [String] p pathname diff --git a/src/include/nfs/ui.rb b/src/include/nfs/ui.rb index 22d3fd7..d49d50d 100644 --- a/src/include/nfs/ui.rb +++ b/src/include/nfs/ui.rb @@ -29,6 +29,7 @@ def initialize_nfs_ui(include_target) Yast.import "FileUtils" Yast.import "Label" Yast.import "Nfs" + Yast.import "NfsOptions" Yast.import "Popup" Yast.import "SuSEFirewall" Yast.import "Wizard" @@ -216,6 +217,7 @@ def GetFstabEntry(fstab_ent, existing) pth = "" mount = "" nfs4 = false + nfs41 = false options = "defaults" servers = [] old = "" @@ -227,6 +229,7 @@ def GetFstabEntry(fstab_ent, existing) mount = Ops.get_string(fstab_ent, "file", "") nfs4 = Ops.get_string(fstab_ent, "vfstype", "") == "nfs4" options = Ops.get_string(fstab_ent, "mntops", "") + nfs41 = nfs4 && NfsOptions.get_nfs41(options) servers = [server] old = Ops.get_string(fstab_ent, "spec", "") else @@ -282,7 +285,14 @@ def GetFstabEntry(fstab_ent, existing) PushButton(Id(:pathent_list), _("&Select")) ) ), - Left(CheckBox(Id(:nfs4), _("NFS&v4 Share"), nfs4)), + Left( + HBox( + CheckBox(Id(:nfs4), _("NFS&v4 Share"), nfs4), + HSpacing(2), + # parallel NFS, protocol version 4.1 + CheckBox(Id(:nfs41), _("pNFS (v4.1)"), nfs41) + ) + ), Left( TextAndButton( InputField( @@ -321,28 +331,9 @@ def GetFstabEntry(fstab_ent, existing) if ret == :choose if @hosts == nil - #newer, shinier, better rpcinfo from rpcbind (#450056) - prog_name = "/sbin/rpcinfo" - delim = "" - - #fallback from glibc (uses different field separators, grr :( ) - if !FileUtils.Exists(prog_name) - prog_name = "/usr/sbin/rpcinfo" - delim = "-d ' ' " - end - # label message UI.OpenDialog(Label(_("Scanning for hosts on this LAN..."))) - # #71064 - # this works also if ICMP broadcasts are ignored - cmd = Ops.add( - Ops.add(Ops.add(prog_name, " -b mountd 1 | cut "), delim), - "-f 2 | sort -u" - ) - out = Convert.to_map(SCR.Execute(path(".target.bash_output"), cmd)) - @hosts = Builtins.filter( - Builtins.splitstring(Ops.get_string(out, "stdout", ""), "\n") - ) { |s| s != "" } + @hosts = Nfs.ProbeServers UI.CloseDialog end if @hosts == [] || @hosts == nil @@ -383,34 +374,9 @@ def GetFstabEntry(fstab_ent, existing) ) ) ) - dirs = [] - - # showmounts does not work for nfsv4 (#466454) - if v4 - tmpdir = Nfs.Mount(server2, "/", nil, "ro", "nfs4") - - # This is completely stupid way how to explore what can be mounted - # and I even don't know if it is correct. Maybe 'find tmpdir -xdev -type d' - # should be used instead. No clue :( - dirs = Builtins.maplist( - Convert.convert( - SCR.Read(path(".target.dir"), tmpdir), - :from => "any", - :to => "list " - ) - ) { |dirent| Ops.add("/", dirent) } - dirs = Builtins.prepend(dirs, "/") - Nfs.Unmount(tmpdir) - else - dirs = Convert.convert( - SCR.Read(path(".net.showexports"), server2), - :from => "any", - :to => "list " - ) - end - - dirs = ["internal error"] if dirs == nil + dirs = Nfs.ProbeExports(server2, v4) UI.CloseDialog + dir = ChooseExport(dirs) UI.ChangeWidget(Id(:pathent), :Value, dir) if dir != nil elsif ret == :browse @@ -434,13 +400,15 @@ def GetFstabEntry(fstab_ent, existing) Convert.to_string(UI.QueryWidget(Id(:mountent), :Value)) ) nfs4 = Convert.to_boolean(UI.QueryWidget(Id(:nfs4), :Value)) + nfs41 = Convert.to_boolean(UI.QueryWidget(Id(:nfs41), :Value)) options = Builtins.deletechars( Convert.to_string(UI.QueryWidget(Id(:optionsent), :Value)), " " ) + options = NfsOptions.set_nfs41(options, nfs41) ret = nil - options_error = check_options(options) + options_error = NfsOptions.validate(options) if !CheckHostName(server) UI.SetFocus(Id(:serverent)) elsif !CheckPath(pth) diff --git a/src/modules/Nfs.rb b/src/modules/Nfs.rb index e2780fc..4330707 100644 --- a/src/modules/Nfs.rb +++ b/src/modules/Nfs.rb @@ -23,7 +23,9 @@ def main textdomain "nfs" + Yast.import "FileUtils" Yast.import "Mode" + Yast.import "NfsOptions" Yast.import "Report" Yast.import "Service" Yast.import "Summary" @@ -46,7 +48,7 @@ def main # Required packages @required_packages = ["nfs-client"] - # eg.: [ $["spec": "moon:/cheese", file: "/mooncheese", "mntopts": "defaults"], ...] + # eg.: [ $["spec": "moon:/cheese", file: "/mooncheese", "mntops": "defaults"], ...] @nfs_entries = [] # Read only, intended for checking mount-point uniqueness. @@ -90,34 +92,126 @@ def ReadIdmapd Convert.to_string(SCR.Read(path(".etc.idmapd_conf.value.General.Domain"))) end - # Set module data - # @param [Hash{String => Object}] settings module settings - # @return [void] - def Set(settings) - settings = deep_copy(settings) - if Builtins.haskey(settings, "enable_nfs4") - @nfs4_enabled = Ops.get_boolean(settings, "enable_nfs4", false) - else - @nfs4_enabled = ReadNfs4() + def ValidateAyNfsEntry(entry) + entry = deep_copy(entry) + valid = true + Builtins.foreach(["server_path", "mount_point", "nfs_options"]) do |k| + if !Builtins.haskey(entry, k) + Builtins.y2error("Missing at Import: '%1'.", k) + valid = false + end end + valid + end - if Builtins.haskey(settings, "enable_nfs_gss") - @nfs_gss_enabled = Ops.get_boolean(settings, "enable_nfs_gss", false) - else - @nfs_gss_enabled = ReadNfsGss() + def GetOptionsAndEntriesSLE11(settings, global_options, entries) + settings = deep_copy(settings) + if Builtins.haskey(Ops.get(settings, 0, {}), "enable_nfs4") || + Builtins.haskey(Ops.get(settings, 0, {}), "idmapd_domain") + global_options.value = Ops.get(settings, 0, {}) + settings = Builtins.remove(settings, 0) end - if Builtins.haskey(settings, "idmapd_domain") - @idmapd_domain = Ops.get_string( - settings, - "idmapd_domain", - "localdomain" + entries.value = Convert.convert( + settings, + :from => "list ", + :to => "list >" + ) + + nil + end + + + def GetOptionsAndEntriesMap(settings, global_options, entries) + settings = deep_copy(settings) + global_options.value = Builtins.remove(settings, "nfs_entries") + entries.value = Ops.get_list(settings, "nfs_entries", []) + + nil + end + + # From settings (which is a list in SLE11 but a map in oS: bnc#820989), + # extract the options and the NFS fstab entries. + def GetOptionsAndEntries(any_settings, global_options, entries) + any_settings = deep_copy(any_settings) + # map: oS; + if Ops.is_map?(any_settings) + global_options_ref = arg_ref(global_options.value) + entries_ref = arg_ref(entries.value) + GetOptionsAndEntriesMap( + Convert.to_map(any_settings), + global_options_ref, + entries_ref ) + global_options.value = global_options_ref.value + entries.value = entries_ref.value + elsif Ops.is(any_settings, "list ") + global_options_ref = arg_ref(global_options.value) + entries_ref = arg_ref(entries.value) + GetOptionsAndEntriesSLE11( + Convert.convert(any_settings, :from => "any", :to => "list "), + global_options_ref, + entries_ref + ) + global_options.value = global_options_ref.value + entries.value = entries_ref.value else - @idmapd_domain = ReadIdmapd() + Builtins.y2internal( + "Cannot happen, got neither a map nor a list: %1", + any_settings + ) + end + + nil + end + + # Fill in the defaults for AY profile entries. + def FillEntriesDefaults(entries) + entries = deep_copy(entries) + Builtins.maplist(entries) do |e| + #Backwards compatibility: with FaTE#302031, we support nfsv4 mounts + #thus we need to keep info on nfs version (v3 vs. v4) + #But older AY profiles might not contain this element + #so let's assume nfsv3 in that case (#395850) + Ops.set(e, "vfstype", "nfs") if !Builtins.haskey(e, "vfstype") + deep_copy(e) + end + end + + def ImportAny(settings) + settings = deep_copy(settings) + # ($) since oS-1x.x, settings was changed to be a map, + # which is incompatible with the sle profiles; + # it owuld be nice to make it compatible again + # whjich this code is readyu to, but the Autoyast engine isn't. + global_options = {} + entries = [] + global_options_ref = arg_ref(global_options) + entries_ref = arg_ref(entries) + GetOptionsAndEntries(settings, global_options_ref, entries_ref) + global_options = global_options_ref.value + entries = entries_ref.value + + return false if Builtins.find(entries) { |e| !ValidateAyNfsEntry(e) } != nil + + entries = FillEntriesDefaults(entries) + + @nfs4_enabled = Ops.get_boolean(global_options, "enable_nfs4") do + ReadNfs4() + end + @nfs_gss_enabled = Ops.get_boolean(global_options, "enable_nfs_gss") do + ReadNfsGss() end + @idmapd_domain = Ops.get_string(global_options, "idmapd_domain") do + ReadIdmapd() + end + + # vfstype can override a missing enable_nfs4 + @nfs4_enabled = true if Builtins.find(entries) do |entry| + Ops.get_string(entry, "vfstype", "") == "nfs4" + end != nil - @nfs_entries = Builtins.maplist(Ops.get_list(settings, "nfs_entries", [])) do |entry| + @nfs_entries = Builtins.maplist(entries) do |entry| { "spec" => Ops.get_string(entry, "server_path", ""), "file" => Ops.get_string(entry, "mount_point", ""), @@ -125,46 +219,18 @@ def Set(settings) "mntops" => Ops.get_string(entry, "nfs_options", "") } end - nil + + true end # Get all NFS configuration from a map. # When called by nfs_auto (preparing autoinstallation data) # the map may be empty. - # @param [Hash{String => Object}] settings a map with a single key: nfs_entries + # @param [Hash{String => Object}] settings a map($) of nfs_entries # @return success def Import(settings) settings = deep_copy(settings) - missing = false - Ops.set( - settings, - "nfs_entries", - Builtins.maplist(Ops.get_list(settings, "nfs_entries", [])) do |s| - Builtins.foreach(["server_path", "mount_point", "nfs_options"]) do |k| - if !Builtins.haskey(s, k) - Builtins.y2error("Missing at Import: '%1'.", k) - missing = true - end - end - #Backwards compatibility: with FaTE#302031, we support nfsv4 mounts - #thus we need to keep info on nfs version (v3 vs. v4) - #But older AY profiles might not contain this element - #so let's assume nfsv3 in that case (#395850) - if !Builtins.haskey(s, "vfstype") - Ops.set(s, "vfstype", "nfs") - else - if Ops.get_string(s, "vfstype", "nfs") == "nfs4" - @nfs4_enabled = true - end - end - deep_copy(s) - end - ) - - return false if missing - - Set(settings) - true + ImportAny(settings) end # Dump the NFS settings to a map, for autoinstallation use. @@ -293,11 +359,9 @@ def Read end end - @nfs4_enabled = SCR.Read(path(".sysconfig.nfs.NFS4_SUPPORT")) == "yes" - @nfs_gss_enabled = SCR.Read(path(".sysconfig.nfs.NFS_SECURITY_GSS")) == "yes" - @idmapd_domain = Convert.to_string( - SCR.Read(path(".etc.idmapd_conf.value.General.Domain")) - ) + @nfs4_enabled = ReadNfs4() + @nfs_gss_enabled = ReadNfsGss() + @idmapd_domain = ReadIdmapd() progress_orig = Progress.set(false) SuSEFirewall.Read @@ -513,7 +577,7 @@ def Mount(server, share, mpoint, options, type) # check if options are valid if Ops.greater_than(Builtins.size(options), 0) - if check_options(options) != "" + if NfsOptions.validate(options) != "" Builtins.y2warning("invalid mount options: %1", options) return nil end @@ -634,6 +698,68 @@ def AutoPackages { "install" => @required_packages, "remove" => [] } end + # Probe the LAN for NFS servers. + # Uses RPC broadcast to mountd. + # @return a list of hostnames + def ProbeServers + #newer, shinier, better rpcinfo from rpcbind (#450056) + prog_name = "/sbin/rpcinfo" + delim = "" + + #fallback from glibc (uses different field separators, grr :( ) + if !FileUtils.Exists(prog_name) + prog_name = "/usr/sbin/rpcinfo" + delim = "-d ' ' " + end + + # #71064 + # this works also if ICMP broadcasts are ignored + cmd = Ops.add( + Ops.add(Ops.add(prog_name, " -b mountd 1 | cut "), delim), + "-f 2 | sort -u" + ) + out = Convert.to_map(SCR.Execute(path(".target.bash_output"), cmd)) + hosts = Builtins.filter( + Builtins.splitstring(Ops.get_string(out, "stdout", ""), "\n") + ) { |s| s != "" } + deep_copy(hosts) + end + + # Probe a server for its exports. + # @param [String] server IP or hostname + # @param [Boolean] v4 Use NFSv4? + # @return a list of exported paths + def ProbeExports(server, v4) + dirs = [] + + # showmounts does not work for nfsv4 (#466454) + if v4 + tmpdir = Mount(server, "/", nil, "ro", "nfs4") + + # This is completely stupid way how to explore what can be mounted + # and I even don't know if it is correct. Maybe 'find tmpdir -xdev -type d' + # should be used instead. No clue :( + dirs = Builtins.maplist( + Convert.convert( + SCR.Read(path(".target.dir"), tmpdir), + :from => "any", + :to => "list " + ) + ) { |dirent| Ops.add("/", dirent) } + dirs = Builtins.prepend(dirs, "/") + Unmount(tmpdir) + else + dirs = Convert.convert( + SCR.Read(path(".net.showexports"), server), + :from => "any", + :to => "list " + ) + end + + dirs = ["internal error"] if dirs == nil + deep_copy(dirs) + end + publish :variable => :modified, :type => "boolean" publish :variable => :skip_fstab, :type => "boolean" publish :function => :SetModified, :type => "void ()" @@ -644,7 +770,6 @@ def AutoPackages publish :variable => :nfs4_enabled, :type => "boolean" publish :variable => :nfs_gss_enabled, :type => "boolean" publish :variable => :idmapd_domain, :type => "string" - publish :function => :Set, :type => "void (map )" publish :function => :Import, :type => "boolean (map )" publish :function => :Export, :type => "map ()" publish :function => :FindPortmapper, :type => "string ()" @@ -655,6 +780,8 @@ def AutoPackages publish :function => :Mount, :type => "string (string, string, string, string, string)" publish :function => :Unmount, :type => "boolean (string)" publish :function => :AutoPackages, :type => "map ()" + publish :function => :ProbeServers, :type => "list ()" + publish :function => :ProbeExports, :type => "list (string, boolean)" end Nfs = NfsClass.new diff --git a/src/modules/NfsOptions.rb b/src/modules/NfsOptions.rb new file mode 100644 index 0000000..ca68e53 --- /dev/null +++ b/src/modules/NfsOptions.rb @@ -0,0 +1,211 @@ +# encoding: utf-8 + +require "yast" + +module Yast + class NfsOptionsClass < Module + def main + textdomain "nfs" + end + + # Parse to an internal representation: + # Simply split by commas, but "defaults" is represented by the empty list + # @param [String] options a fstab option string + # @return [Array] of individual options + def from_string(options) + options = "" if options == "defaults" + Builtins.splitstring(options, ",") + end + + # Convert list of individual options to a fstab option string + # @param [Array] option_list list of individual options + # @return a fstab option string + def to_string(option_list) + option_list = deep_copy(option_list) + options = Builtins.mergestring(option_list, ",") + options = "defaults" if options == "" + options + end + + # Checks the nfs options for /etc/fstab: + # nonempty, comma separated list of foo,nofoo,bar=baz (see nfs(5)) + # @param [String] options options + # @return a translated string with error message, emtpy string if ok + def validate(options) + # To translators: error popup + if Builtins.size(options) == 0 + return _("Empty option strings are not allowed.") + end + + option_list = from_string(options) + + # The options should be kept synced with the code that handles them, + # which is not an easy task, as there are many places: + # - util-linux.rpm + # man 8 mount + # https://git.kernel.org/?p=utils/util-linux/util-linux.git;a=history;f=libmount/src/optmap.c + # - nfs-client.rpm (nfs-utils.src.rpm) + # man 5 nfs + # http://git.linux-nfs.org/?p=steved/nfs-utils.git;a=history;f=utils/mount/nfsmount.c + # - kernel: fs/nfs/super.c + # http://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git;a=history;f=fs/nfs/super.c + # Note that minorversion in particular is mentioned only in the kernel + # but not in nfs-utils. WTF. + + # these can be negated by "no" + _NEGATABLE_OPTIONS = [ + "bg", + "fg", + "soft", + "hard", + "intr", + "posix", + "cto", + "ac", + "acl", + "lock", + "tcp", + "udp", + "rdirplus", + # these are common for all fs types + "atime", + "auto", + "dev", + "exec", + "group", + "owner", + "suid", + "user", + "users" + ] + _NEGATED_OPTIONS = Builtins.maplist(_NEGATABLE_OPTIONS) do |e| + Builtins.sformat("no%1", e) + end + + # these cannot be negated + # they are not nfs specific BTW + _SIMPLE_OPTIONS = [ + "defaults", + "async", + "sync", + "dirsync", + "ro", + "rw", + "remount", + "bind", + "rbind", + "_netdev" + ] + _OPTIONS_WITH_VALUE = [ + "rsize", + "wsize", + "timeo", + "retrans", + "acregmin", + "acregmax", + "acdirmin", + "acdirmin", + "acdirmax", + "actimeo", + "retry", + "namlen", + "port", + "proto", + "clientaddr", + "mountport", + "mounthost", + "mountprog", + "mountvers", + "nfsprog", + "nfsvers", + "vers", + "minorversion", + "sec" + ] + + # first fiter out non value options and its nooptions forms (see nfs(5)) + option_list = Builtins.filter(option_list) do |e| + !Builtins.contains(_NEGATABLE_OPTIONS, e) + end + option_list = Builtins.filter(option_list) do |e| + !Builtins.contains(_NEGATED_OPTIONS, e) + end + option_list = Builtins.filter(option_list) do |e| + !Builtins.contains(_SIMPLE_OPTIONS, e) + end + + error_message = "" + Builtins.foreach(option_list) do |opt| + opt_tuple = Builtins.splitstring(opt, "=") + key = Ops.get(opt_tuple, 0, "") + value = Ops.get(opt_tuple, 1, "") + # By now we have filtered out known options without values; + # so what is left is either unknown options, ... + # FIXME: this also triggers for "intr=bogus" + # because we should have considered '=' before the simple options + # FIXME "'" + foo + "'" used not to break translations; merge it. + if !Builtins.contains(_OPTIONS_WITH_VALUE, key) + # To translators: error popup + error_message = Builtins.sformat( + _("Unknown option: %1"), + Ops.add(Ops.add("'", key), "'") + ) + # ... or known ones with badly specified values + elsif Builtins.size(opt_tuple) != 2 + # To translators: error popup + error_message = Builtins.sformat( + _("Invalid option: %1"), + Ops.add(Ops.add("'", opt), "'") + ) + elsif value == "" + # To translators: error popup + error_message = Builtins.sformat( + _("Empty value for option: %1"), + Ops.add(Ops.add("'", key), "'") + ) + end + raise Break if error_message != "" + end + + error_message + end + + # FIXME: factor out get_nfs4(vfstype, options) (depending on n::o)! + # * @param options fstab option string + # * @return is version >= 4.1 enabled + def get_nfs41(options) + option_list = from_string(options) + + _ENABLED = "minorversion=1" + Builtins.contains(option_list, _ENABLED) + end + + # Add or remove minorversion=1 according to nfs41. + # FIXME vfstype=nfs4 is deprecated in favor of nfsvers=4 (aka vers=4) + # @param [String] options fstab option string + # @param [Boolean] nfs41 is version >= 4.1 enabled + # @return new fstab option string + def set_nfs41(options, nfs41) + # don't mutate the string unnecessarily + return options if get_nfs41(options) == nfs41 + + _ENABLED = "minorversion=1" + _DISABLED = "minorversion=0" + + option_list = from_string(options) + option_list = Builtins.filter(option_list) { |opt| opt != _ENABLED } + option_list = Builtins.filter(option_list) { |opt| opt != _DISABLED } + + option_list = Builtins.add(option_list, _ENABLED) if nfs41 + + to_string(option_list) + end + + publish :function => :validate, :type => "string (string)" + publish :function => :get_nfs41, :type => "boolean (string)" + publish :function => :set_nfs41, :type => "string (string, boolean)" + end + + NfsOptions = NfsOptionsClass.new + NfsOptions.main +end diff --git a/testsuite/.cvsignore b/testsuite/.cvsignore deleted file mode 100644 index 24f1e26..0000000 --- a/testsuite/.cvsignore +++ /dev/null @@ -1,11 +0,0 @@ -Makefile -Makefile.in -site.exp -*.sum -*.log -tmp.out* -tmp.err* -tmp.log* -config -run -*.test diff --git a/testsuite/.gitignore b/testsuite/.gitignore new file mode 100644 index 0000000..30f97be --- /dev/null +++ b/testsuite/.gitignore @@ -0,0 +1,12 @@ +# dejagnu +site.exp +# * is the rpm name +*.log +*.sum +*.test/ +# * are test cases +tmp.out* +tmp.err* +# harness +config/ +run/ diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am index e85fbc8..40dc3df 100644 --- a/testsuite/Makefile.am +++ b/testsuite/Makefile.am @@ -1,8 +1,9 @@ # # Makefile.am for .../testsuite # -# Do not edit this file (Makefile.am) as it will be overwritten! -# + +# enable make check in the working copy +export Y2DIR = $(top_srcdir)/src AUTOMAKE_OPTIONS = dejagnu EXTRA_DIST = $(wildcard tests/*.out) $(wildcard tests/*.err) $(wildcard tests/*.rb) diff --git a/testsuite/tests/autoyast.err b/testsuite/tests/autoyast.err new file mode 100644 index 0000000..e69de29 diff --git a/testsuite/tests/autoyast.out b/testsuite/tests/autoyast.out new file mode 100644 index 0000000..8e33e60 --- /dev/null +++ b/testsuite/tests/autoyast.out @@ -0,0 +1,28 @@ +Read .target.tmpdir "/tmp" +Dump Nfs::Import +Dump - basic, SLE11-SP2 +Read .sysconfig.nfs.NFS4_SUPPORT "no" +Read .sysconfig.nfs.NFS_SECURITY_GSS "no" +Read .etc.idmapd_conf.value.General.Domain "localdomain" +Return true +Dump -- and Export +Dump - empty +Read .sysconfig.nfs.NFS4_SUPPORT "no" +Read .sysconfig.nfs.NFS_SECURITY_GSS "no" +Read .etc.idmapd_conf.value.General.Domain "localdomain" +Return true +Dump - invalid, missing basic data +Log Missing at Import: 'mount_point'. +Log Missing at Import: 'nfs_options'. +Dump - basic, SLE11-SP3 +Read .sysconfig.nfs.NFS_SECURITY_GSS "no" +Return true +Dump -- and Export +Dump - NFSv4 via vfstype +Read .sysconfig.nfs.NFS4_SUPPORT "no" +Read .sysconfig.nfs.NFS_SECURITY_GSS "no" +Return true +Dump -- and Export +Dump - with GSS +Return true +Dump -- and Export diff --git a/testsuite/tests/autoyast.rb b/testsuite/tests/autoyast.rb new file mode 100644 index 0000000..ee45244 --- /dev/null +++ b/testsuite/tests/autoyast.rb @@ -0,0 +1,155 @@ +# encoding: utf-8 + +module Yast + class AutoyastClient < Client + def main + Yast.include self, "testsuite.rb" + @I_READ = { "target" => { "tmpdir" => "/tmp" } } + @I_WRITE = {} + @I_EXEC = {} + TESTSUITE_INIT([@I_READ, @I_WRITE, @I_EXEC], nil) + + @READ = { + "etc" => { "idmapd_conf" => "localdomain" }, + "sysconfig" => { + "nfs" => { "NFS4_SUPPORT" => "no", "NFS_SECURITY_GSS" => "no" } + } + } + + Yast.import "Nfs" + Yast.import "Assert" + + DUMP("Nfs::Import") + # --------- + DUMP("- basic, SLE11-SP2") + @entry1 = { + "server_path" => "data.example.com:/mirror", + "mount_point" => "/mirror", + "nfs_options" => "defaults" + } + + TEST(lambda { Nfs.ImportAny([@entry1]) }, [@READ, {}, {}], nil) + Assert.Equal(1, Builtins.size(Nfs.nfs_entries)) + Assert.Equal( + "data.example.com:/mirror", + Ops.get_string(Nfs.nfs_entries, [0, "spec"], "") + ) + + DUMP("-- and Export") + @ex = Nfs.Export + @e = Ops.get_list(@ex, "nfs_entries", []) + Assert.Equal(1, Builtins.size(@e)) + Assert.Equal(true, Builtins.haskey(@ex, "enable_nfs4")) + Assert.Equal(true, Builtins.haskey(@ex, "idmapd_domain")) + Assert.Equal( + "data.example.com:/mirror", + Ops.get_string(@e, [0, "server_path"], "") + ) + Assert.Equal("/mirror", Ops.get_string(@e, [0, "mount_point"], "")) + Assert.Equal("defaults", Ops.get_string(@e, [0, "nfs_options"], "")) + + # --------- + DUMP("- empty") + TEST(lambda { Nfs.ImportAny([]) }, [@READ, {}, {}], nil) + Assert.Equal(0, Builtins.size(Nfs.nfs_entries)) + + # --------- + DUMP("- invalid, missing basic data") + @entry_invalid = { "server_path" => "data.example.com:/mirror" } + + Nfs.ImportAny([@entry_invalid]) + Assert.Equal(0, Builtins.size(Nfs.nfs_entries)) + + # --------- + DUMP("- basic, SLE11-SP3") + @global_options = { + "enable_nfs4" => true, + "idmapd_domain" => "example.com" + } + TEST(lambda { Nfs.ImportAny([@global_options, @entry1]) }, [@READ, {}, {}], nil) + Assert.Equal(true, Nfs.nfs4_enabled) + Assert.Equal("example.com", Nfs.idmapd_domain) + Assert.Equal(1, Builtins.size(Nfs.nfs_entries)) + Assert.Equal( + "data.example.com:/mirror", + Ops.get_string(Nfs.nfs_entries, [0, "spec"], "") + ) + + DUMP("-- and Export") + @ex = Nfs.Export + @e = Ops.get_list(@ex, "nfs_entries", []) + Assert.Equal(1, Builtins.size(@e)) + Assert.Equal(true, Ops.get_boolean(@ex, "enable_nfs4", false)) + Assert.Equal("example.com", Ops.get_string(@ex, "idmapd_domain", "")) + Assert.Equal( + "data.example.com:/mirror", + Ops.get_string(@e, [0, "server_path"], "") + ) + Assert.Equal("/mirror", Ops.get_string(@e, [0, "mount_point"], "")) + Assert.Equal("defaults", Ops.get_string(@e, [0, "nfs_options"], "")) + + # --------- + DUMP("- NFSv4 via vfstype") + @global_options2 = { "idmapd_domain" => "example.com" } + @entry2 = { + "server_path" => "data.example.com:/mirror", + "mount_point" => "/mirror", + "nfs_options" => "defaults", + "vfstype" => "nfs4" + } + + TEST(lambda { Nfs.ImportAny([@global_options2, @entry2]) }, [@READ, {}, {}], nil) + + Assert.Equal(true, Nfs.nfs4_enabled) + Assert.Equal("example.com", Nfs.idmapd_domain) + Assert.Equal(1, Builtins.size(Nfs.nfs_entries)) + Assert.Equal( + "data.example.com:/mirror", + Ops.get_string(Nfs.nfs_entries, [0, "spec"], "") + ) + + DUMP("-- and Export") + @ex = Nfs.Export + @e = Ops.get_list(@ex, "nfs_entries", []) + Assert.Equal(1, Builtins.size(@e)) + Assert.Equal(true, Ops.get_boolean(@ex, "enable_nfs4", false)) + Assert.Equal("example.com", Ops.get_string(@ex, "idmapd_domain", "")) + Assert.Equal( + "data.example.com:/mirror", + Ops.get_string(@e, [0, "server_path"], "") + ) + Assert.Equal("/mirror", Ops.get_string(@e, [0, "mount_point"], "")) + Assert.Equal("defaults", Ops.get_string(@e, [0, "nfs_options"], "")) + + # --------- + DUMP("- with GSS") + @global_options = { + "enable_nfs4" => true, + "enable_nfs_gss" => true, + "idmapd_domain" => "example.com" + } + TEST(lambda { Nfs.ImportAny([@global_options, @entry1]) }, [@READ, {}, {}], nil) + # assertions shortened + Assert.Equal(true, Nfs.nfs_gss_enabled) + Assert.Equal(1, Builtins.size(Nfs.nfs_entries)) + Assert.Equal( + "data.example.com:/mirror", + Ops.get_string(Nfs.nfs_entries, [0, "spec"], "") + ) + + DUMP("-- and Export") + @ex = Nfs.Export + @e = Ops.get_list(@ex, "nfs_entries", []) + Assert.Equal(1, Builtins.size(@e)) + Assert.Equal(true, Ops.get_boolean(@ex, "enable_nfs_gss", false)) + Assert.Equal( + "data.example.com:/mirror", + Ops.get_string(@e, [0, "server_path"], "") + ) + + nil + end + end +end + +Yast::AutoyastClient.new.main diff --git a/testsuite/tests/nfs-options.err b/testsuite/tests/nfs-options.err new file mode 100644 index 0000000..e69de29 diff --git a/testsuite/tests/nfs-options.out b/testsuite/tests/nfs-options.out new file mode 100644 index 0000000..9e08680 --- /dev/null +++ b/testsuite/tests/nfs-options.out @@ -0,0 +1,15 @@ +Dump NfsOptions::validate +Return Empty option strings are not allowed. +Return +Return +Return +Return +Return Unknown option: ' bg' +Return Unknown option: 'unknownoption' +Return Unknown option: 'unknownassignment' +Return Empty value for option: 'rsize' +Return Unknown option: 'two' +Return Invalid option: 'retrans=trans=trans' +Return Unknown option: 'intr' +Dump NfsOptions::get_nfs41 +Dump NfsOptions::set_nfs41 diff --git a/testsuite/tests/nfs-options.rb b/testsuite/tests/nfs-options.rb new file mode 100644 index 0000000..eb5fa3c --- /dev/null +++ b/testsuite/tests/nfs-options.rb @@ -0,0 +1,96 @@ +# encoding: utf-8 + +module Yast + class NfsOptionsClient < Client + def main + Yast.include self, "testsuite.rb" + Yast.import "NfsOptions" + Yast.import "Assert" + + DUMP("NfsOptions::validate") + TEST(lambda { NfsOptions.validate("") }, [], nil) + TEST(lambda { NfsOptions.validate("defaults") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock,bg") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock,nobg") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock,rsize=8192") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock, bg") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock,unknownoption") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock,unknownassignment=true") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock,rsize=") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock,two=equal=signs") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock,retrans=trans=trans") }, [], nil) + TEST(lambda { NfsOptions.validate("nolock,intr=bogus") }, [], nil) + + DUMP("NfsOptions::get_nfs41") + Assert.Equal(false, NfsOptions.get_nfs41("")) + Assert.Equal(false, NfsOptions.get_nfs41("defaults")) + Assert.Equal(false, NfsOptions.get_nfs41("ro,sync")) + Assert.Equal(false, NfsOptions.get_nfs41("minorversion=0")) + Assert.Equal(true, NfsOptions.get_nfs41("minorversion=1")) + # "minorversion=2" does not exist yet, YAGNI + Assert.Equal(false, NfsOptions.get_nfs41("subminorversion=1")) # substring must not match + # Assert::Equal(?, NfsOptions::get_nfs41("minorversion=1,minorversion=0")); // don't care + Assert.Equal(false, NfsOptions.get_nfs41("ro,minorversion=0,sync")) + Assert.Equal(true, NfsOptions.get_nfs41("ro,minorversion=1,sync")) + + DUMP("NfsOptions::set_nfs41") + Assert.Equal("", NfsOptions.set_nfs41("", false)) + Assert.Equal("minorversion=1", NfsOptions.set_nfs41("", true)) + + Assert.Equal("defaults", NfsOptions.set_nfs41("defaults", false)) + Assert.Equal("minorversion=1", NfsOptions.set_nfs41("defaults", true)) + + Assert.Equal("ro,sync", NfsOptions.set_nfs41("ro,sync", false)) + Assert.Equal( + "ro,sync,minorversion=1", + NfsOptions.set_nfs41("ro,sync", true) + ) + + Assert.Equal( + "minorversion=0", + NfsOptions.set_nfs41("minorversion=0", false) + ) + Assert.Equal( + "minorversion=1", + NfsOptions.set_nfs41("minorversion=0", true) + ) + + Assert.Equal("defaults", NfsOptions.set_nfs41("minorversion=1", false)) + Assert.Equal( + "minorversion=1", + NfsOptions.set_nfs41("minorversion=1", true) + ) + + Assert.Equal( + "subminorversion=1", + NfsOptions.set_nfs41("subminorversion=1", false) + ) + Assert.Equal( + "subminorversion=1,minorversion=1", + NfsOptions.set_nfs41("subminorversion=1", true) + ) + + Assert.Equal( + "ro,minorversion=0,sync", + NfsOptions.set_nfs41("ro,minorversion=0,sync", false) + ) + Assert.Equal( + "ro,sync,minorversion=1", + NfsOptions.set_nfs41("ro,minorversion=0,sync", true) + ) + + Assert.Equal( + "ro,sync", + NfsOptions.set_nfs41("ro,minorversion=1,sync", false) + ) + Assert.Equal( + "ro,minorversion=1,sync", + NfsOptions.set_nfs41("ro,minorversion=1,sync", true) + ) + + nil + end + end +end + +Yast::NfsOptionsClient.new.main diff --git a/testsuite/tests/r-check.out b/testsuite/tests/r-check.out index 4047e4f..f134acf 100644 --- a/testsuite/tests/r-check.out +++ b/testsuite/tests/r-check.out @@ -1,14 +1,3 @@ -Dump check_options -Return Empty option strings are not allowed. -Return -Return -Return -Return -Return Unknown option: bg -Return Unknown option: unknownoption -Return Unknown option: unknownassignment -Return Empty value for option: rsize -Return Unknown option: two Dump CheckHostName Return true Log The hostname entered is invalid. It must be diff --git a/testsuite/tests/r-check.rb b/testsuite/tests/r-check.rb index 80bec07..c0aad51 100644 --- a/testsuite/tests/r-check.rb +++ b/testsuite/tests/r-check.rb @@ -31,18 +31,6 @@ def main @IPv6_link_local_ib = "[fe80::3%eth0]" @IPv6_link_local_invalid = "[fe80::3%]" - DUMP("check_options") - TEST(lambda { check_options("") }, [], nil) - TEST(lambda { check_options("defaults") }, [], nil) - TEST(lambda { check_options("nolock,bg") }, [], nil) - TEST(lambda { check_options("nolock,nobg") }, [], nil) - TEST(lambda { check_options("nolock,rsize=8192") }, [], nil) - TEST(lambda { check_options("nolock, bg") }, [], nil) - TEST(lambda { check_options("nolock,unknownoption") }, [], nil) - TEST(lambda { check_options("nolock,unknownassignment=true") }, [], nil) - TEST(lambda { check_options("nolock,rsize=") }, [], nil) - TEST(lambda { check_options("nolock,two=equal=signs") }, [], nil) - DUMP("CheckHostName") TEST(lambda { CheckHostName(@OK_Name) }, [], nil) TEST(lambda { CheckHostName(@TooLongName) }, [], nil) diff --git a/yast2-nfs-client.spec.in b/yast2-nfs-client.spec.in index b047a64..b895cd8 100644 --- a/yast2-nfs-client.spec.in +++ b/yast2-nfs-client.spec.in @@ -11,7 +11,7 @@ BuildRequires: yast2 >= 2.23.23 # As soon as nfs-utils reaches version 1.2.9 there should be another update. BuildRequires: nfs-client < 1.2.9 #ag_showexports moved to yast2 base -#Wizard::SetDesktopTitleAndIcon +# introduces extended IPv6 support. Requires: yast2 >= 2.23.6 #idmapd_conf agent Requires: yast2-nfs-common >= 2.24.0 @@ -54,6 +54,7 @@ file system access. It allows access to files on remote machines. @clientdir@/nfs-client4part.rb %dir @moduledir@ @moduledir@/Nfs.rb +@moduledir@/NfsOptions.rb %dir @desktopdir@ @desktopdir@/nfs.desktop %doc @docdir@