• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / yast-kdump / 3657353372

pending completion
3657353372

push

github

Unknown Committer
Unknown Commit Message

38 of 38 new or added lines in 5 files covered. (100.0%)

729 of 1827 relevant lines covered (39.9%)

4.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.8
/src/modules/Kdump.rb
1
# encoding: utf-8
2

3
# ------------------------------------------------------------------------------
4
# Copyright (c) 2006 Novell, Inc. All Rights Reserved.
5
#
6
#
7
# This program is free software; you can redistribute it and/or modify it under
8
# the terms of version 2 of the GNU General Public License as published by the
9
# Free Software Foundation.
10
#
11
# This program is distributed in the hope that it will be useful, but WITHOUT
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License along with
16
# this program; if not, contact Novell, Inc.
17
#
18
# To contact Novell about this file by physical or electronic mail, you may find
19
# current contact information at www.novell.com.
20
# ------------------------------------------------------------------------------
21

22
# File:        modules/Kdump.ycp
23
# Package:        Configuration of kdump
24
# Summary:        Kdump settings, input and output functions
25
# Authors:        Jozef Uhliarik <juhliarik@suse.com>
26
#
27
# $Id: Kdump.ycp 27914 2006-02-13 14:32:08Z locilka $
28
#
29
# Representation of the configuration of kdump.
30
# Input and output routines.
31
require "yast"
1✔
32
require "kdump/kdump_system"
1✔
33
require "kdump/kdump_calibrator"
1✔
34

35
require "shellwords"
1✔
36

37
module Yast
1✔
38
  class KdumpClass < Module
1✔
39
    include Yast::Logger
1✔
40

41
    FADUMP_KEY = "KDUMP_FADUMP".freeze
1✔
42
    KDUMP_SERVICE_NAME = "kdump".freeze
1✔
43
    KDUMP_PACKAGES = ["kexec-tools", "kdump"].freeze
1✔
44
    TEMPORARY_CONFIG_FILE = "/var/lib/YaST2/kdump.sysconfig".freeze
1✔
45
    TEMPORARY_CONFIG_PATH = Path.new(".temporary.sysconfig.kdump")
1✔
46

47
    # Space on disk reserved for dump additionally to memory size in bytes
48
    # @see FATE #317488
49
    RESERVED_DISK_SPACE_BUFFER_B = 4 * 1024**3
1✔
50

51
    def main
1✔
52
      textdomain "kdump"
1✔
53

54
      Yast.import "Arch"
1✔
55
      Yast.import "Bootloader"
1✔
56
      Yast.import "Directory"
1✔
57
      Yast.import "FileUtils"
1✔
58
      Yast.import "Map"
1✔
59
      Yast.import "Message"
1✔
60
      Yast.import "Mode"
1✔
61
      Yast.import "Package"
1✔
62
      Yast.import "PackagesProposal"
1✔
63
      Yast.import "Popup"
1✔
64
      Yast.import "ProductControl"
1✔
65
      Yast.import "ProductFeatures"
1✔
66
      Yast.import "Progress"
1✔
67
      Yast.import "Report"
1✔
68
      Yast.import "Service"
1✔
69
      Yast.import "SpaceCalculation"
1✔
70
      Yast.import "String"
1✔
71
      Yast.import "Summary"
1✔
72

73
      reset
1✔
74
    end
75

76
    def reset
1✔
77
      # Data was modified?
78
      @modified = false
91✔
79

80
      # kdump config file
81

82
      @kdump_file = "/etc/sysconfig/kdump"
91✔
83

84
      @proposal_valid = false
91✔
85

86
      # true if propose was called
87
      @propose_called = false
91✔
88

89
      # Boolean option indicates that "crashkernel" includes
90
      # several values for the same kind of memory (low, high)
91
      # or several ranges in one of the values
92
      #
93
      # boolean true if there are several ranges (>1) or overriden values
94
      @crashkernel_list_ranges = false
91✔
95

96
      #  list of packages for installation
97
      @kdump_packages = []
91✔
98

99
      # Boolean option indicates kernel parameter
100
      # "crashkernel"
101
      #
102
      # boolean true if kernel parameter is set
103
      @crashkernel_param = false
91✔
104

105
      # Array (or String) with the values of the kernel parameter
106
      # "crashkernel"
107
      # It can also contain :missing or :present.
108
      # See Yast::Bootloader.kernel_param for details about those special values
109
      #
110
      # array values of kernel parameter
111
      @crashkernel_param_values = []
91✔
112

113
      # array values of kernel parameter for Xen hypervisor
114
      # see @crashkernel_param_values for details
115
      @crashkernel_xen_param_values = []
91✔
116

117
      # Boolean option indicates add kernel param
118
      # "crashkernel"
119
      #
120
      # boolean true if kernel parameter will be add
121
      @add_crashkernel_param = false
91✔
122

123
      # Set of values (high and low) for allocation of memory for boot param
124
      # "crashkernel"
125
      #
126
      # hash with up to two keys (:low and :high) and string values
127
      @allocated_memory = {}
91✔
128

129
      # Boolean option indicates that Import()
130
      # was called and data was proposed
131
      #
132
      # boolean true if import was called with data
133

134
      @import_called = false
91✔
135

136
      # Write only, used during autoinstallation/autoupgrade.
137
      # Don't run services and SuSEconfig, it's all done at one place.
138
      @write_only = false
91✔
139

140
      # Abort function
141
      # return boolean return true if abort
142
      @AbortFunction = nil
91✔
143

144
      # map of deafult values for options in UI
145
      #
146
      # global map <string, string >
147

148
      @DEFAULT_CONFIG = {
149
        "KDUMP_KERNELVER"          => "",
91✔
150
        "KDUMP_CPUS"               => "",
151
        "KDUMP_COMMANDLINE"        => "",
152
        "KDUMP_COMMANDLINE_APPEND" => "",
153
        "KDUMP_AUTO_RESIZE"        => "no",
154
        "KEXEC_OPTIONS"            => "",
155
        "KDUMP_IMMEDIATE_REBOOT"   => "yes",
156
        "KDUMP_COPY_KERNEL"        => "yes",
157
        "KDUMP_TRANSFER"           => "",
158
        "KDUMP_SAVEDIR"            => "file:///var/crash",
159
        "KDUMP_KEEP_OLD_DUMPS"     => "5",
160
        "KDUMP_FREE_DISK_SIZE"     => "64",
161
        "KDUMP_VERBOSE"            => "3",
162
        "KDUMP_DUMPLEVEL"          => "31",
163
        "KDUMP_DUMPFORMAT"         => "lzo",
164
        "KDUMP_CONTINUE_ON_ERROR"  => "true",
165
        "KDUMP_REQUIRED_PROGRAMS"  => "",
166
        "KDUMP_PRESCRIPT"          => "",
167
        "KDUMP_POSTSCRIPT"         => "",
168
        "KDUMPTOOL_FLAGS"          => "",
169
        "KDUMP_NETCONFIG"          => "auto",
170
        "KDUMP_NET_TIMEOUT"        => "30",
171
        "KDUMP_SMTP_SERVER"        => "",
172
        "KDUMP_SMTP_USER"          => "",
173
        "KDUMP_SMTP_PASSWORD"      => "",
174
        "KDUMP_NOTIFICATION_TO"    => "",
175
        "KDUMP_NOTIFICATION_CC"    => "",
176
        "KDUMP_HOST_KEY"           => ""
177
      }
178

179
      # map <string, string > of kdump settings
180
      #
181
      @KDUMP_SETTINGS = {}
91✔
182

183
      # initial kdump settings replaced in Read function
184
      @initial_kdump_settings = deep_copy(@KDUMP_SETTINGS)
91✔
185
    end
186

187
    # Abort function
188
    # @return [Boolean] return true if abort
189
    def Abort
1✔
190
      return @AbortFunction.call == true if !@AbortFunction.nil?
×
191
      false
×
192
    end
193

194
    # Data was modified?
195
    # @return true if modified
196
    def GetModified
1✔
197
      Builtins.y2debug("modified=%1", @modified)
1✔
198
      @modified
1✔
199
    end
200

201
    # Set data was modified
202
    def SetModified
1✔
203
      @modified = true
1✔
204
      Builtins.y2debug("modified=%1", @modified)
1✔
205

206
      nil
207
    end
208

209
    # Function set permission for file.
210
    #
211
    # @return        [Boolean] true on success
212
    # @param        string file name
213
    # @param [String] permissions
214
    #
215
    # @example
216
    #        FileUtils::Chmod ("/etc/sysconfig/kdump", "600") -> true
217
    #        FileUtils::Chmod ("/tmp/doesnt_exist", "644") -> false
218
    def Chmod(target, permissions)
1✔
219
      if !FileUtils.Exists(target)
×
220
        Builtins.y2error("Target %1 doesn't exist", target)
×
221
        return false
×
222
      end
223

224
      if !FileUtils.Exists("/bin/chmod")
×
225
        Builtins.y2error("tool: /bin/chmod not found")
×
226
        return false
×
227
      end
228

229
      cmd = Builtins.sformat("/bin/chmod %1 %2", permissions.shellescape, target.shellescape)
×
230
      cmd_out = Convert.to_map(SCR.Execute(path(".target.bash_output"), cmd))
×
231

232
      if Ops.get_integer(cmd_out, "exit", -1) != 0
×
233
        Builtins.y2error("Command >%1< returned %2", cmd, cmd_out)
×
234
        return false
×
235
      end
236
      Builtins.y2milestone("Command: %1 finish successful.", cmd)
×
237
      true
×
238
    end
239

240
    # Function check if KDUMP_SAVEDIR or
241
    # KDUMP_SMTP_PASSWORD include password
242
    #
243
    # @return [Boolean] true if inlude password
244

245
    def checkPassword
1✔
246
      return true if Ops.get(@KDUMP_SETTINGS, "KDUMP_SMTP_PASSWORD", "") != ""
×
247

248
      if Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "").include?("file") ||
×
249
          Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "").include?("nfs") ||
250
          Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "") == ""
251
        return false
×
252
      end
253

254
      if !Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "").include?("@")
×
255
        return false
×
256
      end
257

258
      temp = Builtins.splitstring(
×
259
        Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", ""),
260
        "@"
261
      )
262
      temp_1 = Ops.get(temp, 0, "")
×
263
      position = Builtins.findlastof(temp_1, ":")
×
264
      return false if position.nil?
×
265

266
      # if there is 2 times ":" -> it means that password is defined
267
      # for example cifs://login:password@server....
268
      position > 6
×
269
    end
270

271
    # Read current kdump configuration
272
    #
273
    # read kernel parameter "crashkernel"
274
    #  @return [Boolean] successfull
275
    def ReadKdumpKernelParam
1✔
276
      result = Bootloader.kernel_param(:common, "crashkernel")
51✔
277
      xen_result = Bootloader.kernel_param(:xen_host, "crashkernel")
51✔
278
      # result could be [String,Array,:missing,:present]
279
      # String   - the value of the only occurrence
280
      # Array    - the values of the multiple occurrences
281
      # :missing - crashkernel is missed
282
      # :present - crashkernel is defined but no value is available
283

284
      if result == :missing
51✔
285
        @crashkernel_param = false
5✔
286
        @add_crashkernel_param = false
5✔
287
      else
288
        @crashkernel_param = true
46✔
289
        @add_crashkernel_param = true
46✔
290
      end
291

292
      if result == :missing || result == :present
51✔
293
        @crashkernel_param_values = result
8✔
294
      else
295
        # Let's make sure it's an array
296
        # filtering nils and empty entries bnc#991140
297
        @crashkernel_param_values = Array(result).compact.reject(&:empty?)
43✔
298
        # Read the current value only if crashkernel parameter is set.
299
        # (bnc#887901)
300
        @allocated_memory = get_allocated_memory(@crashkernel_param_values)
43✔
301
      end
302

303
      if xen_result == :missing || xen_result == :present
51✔
304
        @crashkernel_xen_param_values = xen_result
8✔
305
      else
306
        # Let's make sure it's an array
307
        # filtering nils and empty entries bnc#991140
308
        @crashkernel_xen_param_values = Array(xen_result).compact.reject(&:empty?)
43✔
309
      end
310

311
      true
51✔
312
    end
313

314
    # Returns the KdumpSystem instance
315
    def system
1✔
316
      @system ||= Yast::KdumpSystem.new
×
317
    end
318

319
    def write_temporary_config_file
1✔
320
      SCR.RegisterAgent(TEMPORARY_CONFIG_PATH,
×
321
        term(:ag_ini,
322
          term(:SysConfigFile, TEMPORARY_CONFIG_FILE)))
323
      WriteKdumpSettingsTo(TEMPORARY_CONFIG_PATH, TEMPORARY_CONFIG_FILE)
×
324
      SCR.UnregisterAgent(TEMPORARY_CONFIG_PATH)
×
325
    end
326

327
    # Return the Kdump calibrator instance
328
    #
329
    # @return [Yast::KdumpCalibrator] Calibrator instance
330
    def calibrator
1✔
331
      return @calibrator unless @calibrator.nil?
82✔
332
      if Mode.normal
1✔
333
        @calibrator = Yast::KdumpCalibrator.new
1✔
334
      else
335
        write_temporary_config_file
×
336
        @calibrator = Yast::KdumpCalibrator.new(TEMPORARY_CONFIG_FILE)
×
337
      end
338
    end
339

340
    # Returns the Kdump memory limits
341
    #
342
    # It relies on the calibrator but it adjust the low memory limits when using firmware-assisted
343
    # dumps. The reason is that those limits might contradict the recommended value. See
344
    # jsc#SLE-21644 for more information.
345
    #
346
    # @return [Hash] The hash contains the following keys: :min_low, :max_low,
347
    #   :default_low, :min_high, :max_high, :default_high, :min_fadump,
348
    #    :max_fadump, :default_fadump
349
    def memory_limits
1✔
350
      calibrator.memory_limits
1✔
351
    end
352

353
    # Propose reserved/allocated memory
354
    # Store the result as a hash to @allocated_memory
355
    # @return [Boolean] true, always successful
356
    def ProposeAllocatedMemory
1✔
357
      # only propose once
358
      return true unless @allocated_memory.empty?
10✔
359

360
      @allocated_memory = { low: calibrator.default_low.to_s, high: calibrator.default_high.to_s }
8✔
361
      Builtins.y2milestone(
8✔
362
        "[kdump] allocated memory if not set in \"crashkernel\" param: %1",
363
        @allocated_memory
364
      )
365
      true
8✔
366
    end
367

368
    # Returns total size of physical memory in MiB
369
    def total_memory
1✔
370
      calibrator.total_memory
1✔
371
    end
372

373
    def log_settings_censoring_passwords(message)
1✔
374
      debug_KDUMP_SETTINGS = deep_copy(@KDUMP_SETTINGS)
2✔
375
      debug_KDUMP_SETTINGS["KDUMP_SAVEDIR"]       = "********"
2✔
376
      debug_KDUMP_SETTINGS["KDUMP_SMTP_PASSWORD"] = "********"
2✔
377

378
      log.info "-------------KDUMP_SETTINGS-------------------"
2✔
379
      log.info "#{message}; here with censored passwords: #{debug_KDUMP_SETTINGS}"
2✔
380
      log.info "---------------------------------------------"
2✔
381
    end
382

383
    # Read current kdump configuration
384
    #
385
    #  @return [Boolean] successful
386
    def ReadKdumpSettings
1✔
387
      @KDUMP_SETTINGS = deep_copy(@DEFAULT_CONFIG)
2✔
388
      SCR.Dir(path(".sysconfig.kdump")).each do |key|
2✔
389
        val = Convert.to_string(
×
390
          SCR.Read(path(".sysconfig.kdump") + key)
391
        )
392
        @KDUMP_SETTINGS[key] = val
×
393
      end
394

395
      log_settings_censoring_passwords("kdump configuration has been read")
2✔
396

397
      @initial_kdump_settings = deep_copy(@KDUMP_SETTINGS)
2✔
398

399
      true
2✔
400
    end
401

402
    # Updates initrd and reports whether it was successful.
403
    # Failed update is reported using Report library.
404
    #
405
    # @return [Boolean] whether successful
406
    def update_initrd
1✔
407
      # when /boot is ro, we need to use transactional update to be able to
408
      # rebuild initrd. In the end tu script below is used, but needs sauce
409
      # around
410
      if Package.IsTransactionalSystem
1✔
411
        return update_initrd_with("transactional-update --continue kdump")
1✔
412
      end
413

414
      # For CaaSP we need an explicit initrd rebuild before the
415
      # first boot, when the root filesystem becomes read only.
416
      rebuild_cmd = "/usr/sbin/tu-rebuild-kdump-initrd"
×
417
      # part of transactional-update.rpm
418
      update_initrd_with("if test -x #{rebuild_cmd}; then #{rebuild_cmd}; fi")
×
419

420
      return true unless using_fadump_changed?
×
421

422
      update_command = "/usr/sbin/mkdumprd -f"
×
423
      update_initrd_with(update_command)
×
424
    end
425

426
    # @param update_command [String] a command for .target.bash
427
    # @return [Boolean] whether successful
428
    def update_initrd_with(update_command)
1✔
429
      update_logfile = File.join(Directory.logdir, "y2logmkinitrd")
×
430

431
      run_command = update_command + " >> #{update_logfile.shellescape} 2>&1"
×
432
      y2milestone("Running command: #{run_command}")
×
433
      ret = SCR.Execute(path(".target.bash"), run_command)
×
434

435
      if ret != 0
×
436
        y2error("Error updating initrd, see #{update_logfile} or call #{update_command} manually")
×
437
        Report.Error(_(
×
438
          "Error updating initrd while calling '%{cmd}'.\n" \
439
          "See %{log} for details."
440
        ) % { :cmd => update_command, :log => update_logfile })
441
        return false
×
442
      end
443

444
      true
×
445
    end
446

447
    # Writes a file in the /etc/sysconfig/kdump format
448
    def WriteKdumpSettingsTo(scr_path, file_name)
1✔
449
      log_settings_censoring_passwords("kdump configuration for writing")
×
450

451
      @KDUMP_SETTINGS.each do |option_key, option_val|
×
452
        SCR.Write(scr_path + option_key, option_val)
×
453
      end
454
      SCR.Write(scr_path, nil)
×
455

456
      if checkPassword
×
457
        Chmod(file_name, "600")
×
458
      else
459
        Chmod(file_name, "644")
×
460
      end
461
    end
462

463
    # Write current kdump configuration
464
    #
465
    #  @return [Boolean] successful
466
    def WriteKdumpSettings
1✔
467
      WriteKdumpSettingsTo(path(".sysconfig.kdump"), @kdump_file)
×
468

469
      update_initrd
×
470
    end
471

472
    # Write kdump boot arguments - crashkernel and fadump
473
    # set kdump start at boot
474
    #
475
    #  @return [Boolean] successfull
476
    def WriteKdumpBootParameter
1✔
477
      reboot_needed = using_fadump_changed?
23✔
478

479
      # First, write or remove the fadump param if needed
480
      write_fadump_boot_param
23✔
481

482
      # Then, do the same for the crashkernel param
483
      #
484
      # If we need to add crashkernel param
485
      if @add_crashkernel_param
23✔
486
        if Mode.autoinst || Mode.autoupgrade
19✔
487
          # Use the value(s) read by import
488
          crash_values = @crashkernel_param_values
12✔
489
          crash_xen_values = @crashkernel_xen_param_values
12✔
490
          # Always write the value
491
          skip_crash_values = false
12✔
492
        else
493
          # Calculate the param values based on @allocated_memory
494
          crash_values = crash_kernel_values
7✔
495
          crash_xen_values = crash_xen_kernel_values
7✔
496
          remove_offsets!(crash_values) if Mode.update
7✔
497
          remove_offsets!(crash_xen_values) if Mode.update
7✔
498
          # Skip writing of param if it's already set to the desired values
499
          skip_crash_values = @crashkernel_param && @crashkernel_param_values == crash_values
7✔
500
          skip_crash_values &&= @crashkernel_xen_param_values && @crashkernel_xen_param_values == crash_xen_values
7✔
501
        end
502

503
        if skip_crash_values
19✔
504
          # start kdump at boot
505
          Service.Enable(KDUMP_SERVICE_NAME)
2✔
506
          Service.Restart(KDUMP_SERVICE_NAME) if Service.active?(KDUMP_SERVICE_NAME)
2✔
507
        else
508
          Bootloader.modify_kernel_params(:common, :recovery, "crashkernel" => crash_values)
17✔
509
          Bootloader.modify_kernel_params(:xen_host, "crashkernel" => crash_xen_values)
17✔
510
          # do mass write in installation to speed up, so skip this one
511
          if !Stage.initial
17✔
512
            old_progress = Progress.set(false)
17✔
513
            Bootloader.Write
17✔
514
            Progress.set(old_progress)
17✔
515
          end
516
          Builtins.y2milestone(
17✔
517
            "[kdump] (WriteKdumpBootParameter) adding crashkernel options with values: %1",
518
            crash_values
519
          )
520
          Builtins.y2milestone(
17✔
521
            "[kdump] (WriteKdumpBootParameter) adding xen crashkernel options with values: %1",
522
            crash_xen_values
523
          )
524
          reboot_needed = true
17✔
525
          Service.Enable(KDUMP_SERVICE_NAME)
17✔
526
        end
527
      else
528
        # If we don't need the param but it is there
529
        if @crashkernel_param
4✔
530
          # delete crashkernel parameter from bootloader
531
          Bootloader.modify_kernel_params(:common, :xen_guest, :recovery, :xen_host, "crashkernel" => :missing)
1✔
532
          if !Stage.initial
1✔
533
            old_progress = Progress.set(false)
1✔
534
            Bootloader.Write
1✔
535
            Progress.set(old_progress)
1✔
536
          end
537
          reboot_needed = true
1✔
538
        end
539
        Service.Disable(KDUMP_SERVICE_NAME)
4✔
540
        Service.Stop(KDUMP_SERVICE_NAME) if Service.active?(KDUMP_SERVICE_NAME)
4✔
541
      end
542

543
      if reboot_needed && Mode.normal && !Mode.commandline
23✔
544
        Popup.Message(_("To apply changes a reboot is necessary."))
18✔
545
      end
546

547
      true
23✔
548
    end
549

550
    # Read all kdump settings
551
    # @return true on success
552
    def Read
1✔
553
      # Kdump read dialog caption
554
      caption = _("Initializing kdump Configuration")
×
555
      steps = 4
×
556

557
      Progress.New(
×
558
        caption,
559
        " ",
560
        steps,
561
        [
562
          # Progress stage 1/4
563
          _("Reading the config file..."),
564
          # Progress stage 3/4
565
          _("Reading kernel boot options..."),
566
          # Progress stage 4/4
567
          _("Calculating memory limits...")
568
        ],
569
        [
570
          # Progress step 1/4
571
          _("Reading the config file..."),
572
          # Progress step 2/4
573
          _("Reading partitions of disks..."),
574
          # Progress finished 3/4
575
          _("Reading available memory and calibrating usage..."),
576
          # Progress finished 4/4
577
          Message.Finished
578
        ],
579
        ""
580
      )
581

582
      # read database
583
      return false if Abort()
×
584
      Progress.NextStage
×
585
      # Error message
586
      if !ReadKdumpSettings()
×
587
        Report.Error(_("Cannot read config file /etc/sysconfig/kdump"))
×
588
      end
589

590
      # read another database
591
      return false if Abort()
×
592
      Progress.NextStep
×
593
      # Error message
594
      if !ReadKdumpKernelParam()
×
595
        Report.Error(_("Cannot read kernel boot options."))
×
596
      end
597

598
      # read another database
599
      return false if Abort()
×
600
      Progress.NextStep
×
601
      ProposeAllocatedMemory()
×
602
      # Error message
603
      Report.Error(_("Cannot read available memory.")) if total_memory.zero?
×
604

605
      return false if Abort()
×
606
      # Progress finished
607
      Progress.NextStage
×
608

609
      return false if Abort()
×
610
      @modified = false
×
611
      true
×
612
    end
613

614
    # Update crashkernel argument during update of OS
615
    # @return true on success
616

617
    def Update
1✔
618
      Builtins.y2milestone("Update kdump settings")
2✔
619
      ReadKdumpKernelParam() unless Mode.autoupgrade
2✔
620
      WriteKdumpBootParameter()
2✔
621
      true
2✔
622
    end
623

624
    # Write all kdump settings
625
    # @return true on success
626
    def Write
1✔
627
      # Kdump read dialog caption
628
      caption = _("Saving kdump Configuration")
×
629

630
      # number of stages
631
      steps = 2
×
632
      if (Mode.installation || Mode.autoinst) && !@add_crashkernel_param
×
633
        Builtins.y2milestone(
×
634
          "Skip writing of configuration for kdump during installation"
635
        )
636
        return true
×
637
      end
638

639
      # We do not set help text here, because it was set outside
640
      Progress.New(
×
641
        caption,
642
        " ",
643
        steps,
644
        [
645
          # Progress stage 1/2
646
          _("Write the settings"),
647
          # Progress stage 2/2
648
          _("Update boot options")
649
        ],
650
        [
651
          # Progress step 1/2
652
          _("Writing the settings..."),
653
          # Progress step 2/2
654
          _("Updating boot options..."),
655
          # Progress finished
656
          _("Finished")
657
        ],
658
        ""
659
      )
660

661
      # write settings
662
      return false if Abort()
×
663
      Progress.NextStage
×
664
      # Error message
665
      if !WriteKdumpSettings()
×
666
        Report.Error(_("Cannot write settings."))
×
667
        return false
×
668
      end
669

670
      # write/delete bootloader options for kernel - "crashkernel" and "fadump"
671
      return false if Abort()
×
672
      Progress.NextStage
×
673
      # Error message
674
      if !WriteKdumpBootParameter()
×
675
        Report.Error(_("Adding crashkernel parameter to bootloader fault."))
×
676
      end
677

678
      return false if Abort()
×
679
      # Progress finished
680
      Progress.NextStage
×
681

682
      return false if Abort()
×
683
      true
×
684
    end
685

686
    # Adding necessary packages for installation
687
    #
688

689
    def AddPackages
1✔
690
      return unless Mode.installation
1✔
691

692
      @kdump_packages.concat KDUMP_PACKAGES
×
693
    end
694

695
    # Proposes default state of kdump (enabled/disabled)
696
    #
697
    # @return [Boolean] the default proposed state
698

699
    def ProposeCrashkernelParam
1✔
700
      # proposing disabled kdump if product wants it (bsc#1071242)
701
      if !ProductFeatures.GetBooleanFeature("globals", "enable_kdump")
5✔
702
        log.info "Kdump disabled in control file"
1✔
703
        false
1✔
704
      # proposing disabled kdump if PC has less than 1024MB RAM
705
      elsif total_memory < 1024
4✔
706
        log.info "not enough memory - kdump proposed as disabled"
1✔
707
        false
1✔
708
      # proposing disabled kdump on aarch64 (bsc#989321) - kdump not implemented
709
      elsif Arch.aarch64
3✔
710
        log.info "aarch64 - kdump proposed as disabled"
1✔
711
        false
1✔
712
      else
713
        true
2✔
714
      end
715
    end
716

717
    # Propose global variables once...
718
    # after that remember user settings
719

720
    def ProposeGlobalVars
1✔
721
      # Settings have not been imported by AutoYaST and have not already
722
      # been suggested by proposal. (bnc#930950, bnc#995750, bnc#890719).
723
      if !@propose_called && !@import_called
1✔
724
        # added default settings
725
        @KDUMP_SETTINGS = deep_copy(@DEFAULT_CONFIG)
×
726
        @add_crashkernel_param = ProposeCrashkernelParam()
×
727
        @crashkernel_param = false
×
728
      end
729
      @propose_called = true
1✔
730

731
      nil
732
    end
733

734
    # Check if user enabled kdump
735
    # if no deselect packages for installing
736
    # if yes add necessary packages for installation
737
    def CheckPackages
1✔
738
      # remove duplicates
739
      @kdump_packages.uniq!
1✔
740
      if !@add_crashkernel_param
1✔
741
        Builtins.y2milestone(
×
742
          "deselect packages for installation: %1",
743
          @kdump_packages
744
        )
745
        @kdump_packages.each do |p|
×
746
          PackagesProposal.RemoveResolvables("yast2-kdump", :package, [p])
×
747
        end
748
        if !@kdump_packages.empty?
×
749
          Builtins.y2milestone(
×
750
            "Deselected kdump packages for installation: %1",
751
            @kdump_packages
752
          )
753
        end
754
      else
755
        Builtins.y2milestone(
1✔
756
          "select packages for installation: %1",
757
          @kdump_packages
758
        )
759
        @kdump_packages.each do |p|
1✔
760
          PackagesProposal.AddResolvables("yast2-kdump", :package, [p])
×
761
        end
762
        if !@kdump_packages.empty?
1✔
763
          Builtins.y2milestone(
×
764
            "Selected kdump packages for installation: %1",
765
            @kdump_packages
766
          )
767
        end
768
      end
769

770
      nil
771
    end
772

773
    # Propose all kdump settings
774
    #
775
    def Propose
1✔
776
      Builtins.y2milestone("Proposing new settings of kdump")
1✔
777
      # set default values for global variables
778
      ProposeGlobalVars()
1✔
779
      # check available memory and execute the calibrator
780
      ProposeAllocatedMemory()
1✔
781
      # add packages for installation
782
      AddPackages()
1✔
783
      # select packages for installation
784
      CheckPackages()
1✔
785

786
      nil
787
    end
788

789
    # Create a textual summary
790
    # @return summary of the current configuration
791
    def Summary
1✔
792
      result = []
1✔
793
      result = Builtins.add(
1✔
794
        result,
795
        Builtins.sformat(
796
          _("Kdump status: %1"),
797
          @add_crashkernel_param ? _("enabled") : _("disabled")
1✔
798
        )
799
      )
800
      if @add_crashkernel_param
1✔
801
        result = Builtins.add(
×
802
          result,
803
          Builtins.sformat(
804
            _("Value(s) of crashkernel option: %1"),
805
            crash_kernel_values.join(" ")
806
          )
807
        )
808
        result = Builtins.add(
×
809
          result,
810
          Builtins.sformat(
811
            _("Dump format: %1"),
812
            Ops.get(@KDUMP_SETTINGS, "KDUMP_DUMPFORMAT", "")
813
          )
814
        )
815
        result = Builtins.add(
×
816
          result,
817
          Builtins.sformat(
818
            _("Target of dumps: %1"),
819
            Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "")
820
          )
821
        )
822
        result = Builtins.add(
×
823
          result,
824
          Builtins.sformat(
825
            _("Number of dumps: %1"),
826
            Ops.get(@KDUMP_SETTINGS, "KDUMP_KEEP_OLD_DUMPS", "")
827
          )
828
        )
829
      end
830
      deep_copy(result)
1✔
831
    end
832

833
    # Returns available space (in bytes) for Kernel dump according to KDUMP_SAVEDIR option
834
    # only local space is evaluated (starts with file://)
835
    #
836
    # @return [Integer] free space in bytes or nil if filesystem is not local or no
837
    #                   packages proposal is made yet
838
    def free_space_for_dump_b
1✔
839
      kdump_savedir = @KDUMP_SETTINGS.fetch("KDUMP_SAVEDIR", "file:///var/log/dump")
8✔
840
      log.info "Using savedir #{kdump_savedir}"
8✔
841

842
      if kdump_savedir.start_with?("/")
8✔
843
        log.warn "Using old format"
1✔
844
      elsif kdump_savedir.start_with?("file://")
7✔
845
        kdump_savedir.sub!(/file:\/\//, "")
5✔
846
      else
847
        log.info "KDUMP_SAVEDIR #{kdump_savedir.inspect} is not local"
2✔
848
        return nil
2✔
849
      end
850

851
      # unified format of directory
852
      kdump_savedir = format_dirname(kdump_savedir)
6✔
853

854
      partitions_info = SpaceCalculation.GetPartitionInfo()
6✔
855
      if partitions_info.empty?
6✔
856
        log.warn "No partitions info available"
1✔
857
        return nil
1✔
858
      end
859

860
      log.info "Disk usage: #{partitions_info}"
5✔
861
      # Create a hash of partitions and their free space { partition => free_space, ... }
862
      # "name" usually does not start with "/", but does so for root filesystem
863
      # File.join ensures that paths do not contain dulplicit "/" characters
864
      partitions_info = partitions_info.map do |partition|
5✔
865
        { format_dirname(partition["name"]) => partition["free"] }
15✔
866
      end.inject(:merge)
867

868
      # All partitions matching KDUMP_SAVEDIR
869
      matching_partitions = partitions_info.select do |partition, _space|
5✔
870
        kdump_savedir.start_with?(partition)
15✔
871
      end
872

873
      # The longest match
874
      partition = matching_partitions.keys.sort_by { |partiton| partiton.length }.last
13✔
875
      free_space = matching_partitions[partition]
5✔
876

877
      if free_space.nil? || !free_space.is_a?(::Integer)
5✔
878
        log.warn "Available space for partition #{partition} not provided (#{free_space.inspect})"
2✔
879
        return nil
2✔
880
      end
881

882
      # packager counts in kB, we need bytes
883
      free_space *= 1024
3✔
884
      log.info "Available space for dump: #{free_space} bytes in #{partition} directory"
3✔
885

886
      free_space
3✔
887
    end
888

889
    # Returns disk space in bytes requested for kernel dump (as defined in FATE#317488)
890
    #
891
    # @return [Integer] bytes
892
    def space_requested_for_dump_b
1✔
893
      # Total memory is in MB, converting to bytes
894
      total_memory * 1024**2 + RESERVED_DISK_SPACE_BUFFER_B
1✔
895
    end
896

897
    # Returns installation proposal warning as part of the MakeProposal map result
898
    # includes 'warning' and 'warning_level' keys
899
    #
900
    # @param returns [Hash] with warnings
901
    def proposal_warning
1✔
902
      return {} unless @add_crashkernel_param
3✔
903

904
      free_space = free_space_for_dump_b
2✔
905
      requested_space = space_requested_for_dump_b
2✔
906

907
      log.info "Free: #{free_space}, requested: #{requested_space}"
2✔
908
      return {} if free_space.nil? || requested_space.nil?
2✔
909

910
      warning = {}
2✔
911

912
      if free_space < requested_space
2✔
913
        warning = {
914
          "warning_level" => :warning,
1✔
915
          # TRANSLATORS: warning message in installation proposal. Do not translate %{requested} and
916
          # %{available} - they are replaced with actual sizes later.
917
          "warning"       => "<ul><li>" + _(
918
            "Warning! There might not be enough free space to have kdump enabled. " \
919
            "%{required} required for saving a kernel dump, but only %{available} are available."
920
          ) % {
921
            required:  String.FormatSizeWithPrecision(requested_space, 2, true),
922
            available: String.FormatSizeWithPrecision(free_space, 2, true)
923
          } + "</li></ul>"
924
        }
925
      end
926

927
      log.warn warning["warning"] if warning["warning"]
2✔
928
      warning
2✔
929
    end
930

931
    # bnc# 480466 - fix problem with validation autoyast profil
932
    # Function filters keys for autoyast profil
933
    #
934
    # @param map <string, string > KDUMP_SETTINGS
935
    # @return [Hash{String => String}] filtered KDUMP_SETTINGS by DEFAULT_CONFIG
936

937
    def filterExport(settings)
1✔
938
      settings = deep_copy(settings)
1✔
939
      keys = Map.Keys(@DEFAULT_CONFIG)
1✔
940
      Builtins.filter(settings) do |key, _value|
1✔
941
        Builtins.contains(keys, key)
28✔
942
      end
943
    end
944

945
    # Export kdump settings to a map
946
    # @return kdump settings
947
    def Export
1✔
948
      if @add_crashkernel_param
3✔
949
        crash_kernel = crash_kernel_values
1✔
950
        crash_kernel = crash_kernel[0] if crash_kernel.size == 1
1✔
951
        crash_xen_kernel = crash_xen_kernel_values
1✔
952
        crash_xen_kernel = crash_xen_kernel[0] if crash_xen_kernel.size == 1
1✔
953
        out = {
954
          "crash_kernel"     => crash_kernel,
1✔
955
          "crash_xen_kernel" => crash_xen_kernel,
956
          "add_crash_kernel" => true,
957
          "general"          => filterExport(@KDUMP_SETTINGS)
958
        }
959
      else
960
        out = { "add_crash_kernel" => false }
2✔
961
      end
962

963
      Builtins.y2milestone("Kdump exporting settings: %1", out)
3✔
964
      deep_copy(out)
3✔
965
    end
966

967
    # Import settings from a map
968
    # @param [Hash, nil] settings map of kdump settings
969
    # @return [Boolean] true on success
970
    def Import(settings)
1✔
971
      settings ||= {}
16✔
972
      Builtins.y2milestone("Importing settings for kdump #{settings.inspect}")
16✔
973

974
      my_import_map = Ops.get_map(settings, "general", {})
16✔
975
      @DEFAULT_CONFIG.each_pair do |key, def_value|
16✔
976
        value = my_import_map[key]
448✔
977
        @KDUMP_SETTINGS[key] = value.nil? ? def_value : value
448✔
978
      end
979

980
      if settings.key?("crash_kernel")
16✔
981
        # Make sure it's an array
982
        @crashkernel_param_values = Array(settings.fetch("crash_kernel", ""))
9✔
983
        # In order not to overwrite the values by the proposal we will have to set
984
        # according allocated memory too. (bnc#995750)
985
        @allocated_memory = get_allocated_memory(@crashkernel_param_values)
9✔
986
      else
987
        # Taking proposed values (bnc#997448)
988
        ProposeAllocatedMemory()
7✔
989
        # Make sure it's an array
990
        @crashkernel_param_values = Array(crash_kernel_values)
7✔
991
      end
992

993
      if settings.key?("crash_xen_kernel")
16✔
994
        # Make sure it's an array
995
        @crashkernel_xen_param_values = Array(settings.fetch("crash_xen_kernel", ""))
6✔
996
      else
997
        @crashkernel_xen_param_values = Array(crash_xen_kernel_values)
10✔
998
      end
999

1000
      @add_crashkernel_param = if settings.key?("add_crash_kernel")
16✔
1001
        settings["add_crash_kernel"]
15✔
1002
      else
1003
        ProposeCrashkernelParam()
1✔
1004
      end
1005

1006
      if settings.key?("crash_kernel") || settings.key?("add_crash_kernel") ||
16✔
1007
          !my_import_map.empty?
1008
        @import_called = true
15✔
1009
      end
1010

1011
      true
16✔
1012
    end
1013

1014
    # Sets whether to use FADump (Firmware assisted dump)
1015
    #
1016
    # @param [Boolean] new state
1017
    # @return [Boolean] whether successfully set
1018
    def use_fadump(new_value)
1✔
1019
      # Trying to use fadump on unsupported hardware
1020
      if !fadump_supported? && new_value
7✔
1021
        Builtins.y2milestone("FADump is not supported on this hardware")
1✔
1022
        Report.Error(_("Cannot use Firmware-assisted dump.\nIt is not supported on this hardware."))
1✔
1023
        return false
1✔
1024
      end
1025

1026
      @KDUMP_SETTINGS[FADUMP_KEY] = (new_value ? "yes" : "no")
6✔
1027
      true
6✔
1028
    end
1029

1030
    # Returns whether FADump (Firmware assisted dump) is currently in use
1031
    #
1032
    # @return [Boolean] currently in use
1033
    def using_fadump?
1✔
1034
      @KDUMP_SETTINGS[FADUMP_KEY] == "yes"
3✔
1035
    end
1036

1037
    # Has the using_fadump? been changed?
1038
    #
1039
    # @return [Boolean] whether changed
1040
    def using_fadump_changed?
1✔
1041
      @initial_kdump_settings[FADUMP_KEY] != @KDUMP_SETTINGS[FADUMP_KEY]
25✔
1042
    end
1043

1044
    # Returns whether usage of high memory in the crashkernel bootloader param
1045
    # is supported by the current system
1046
    #
1047
    # @return [Boolean] is supported
1048
    def high_memory_supported?
1✔
1049
      calibrator.high_memory_supported?
×
1050
    end
1051

1052
    # Returns whether usage of fadump is supported by the current system
1053
    #
1054
    # @return [Boolean] is supported
1055
    def fadump_supported?
1✔
1056
      calibrator.fadump_supported?
5✔
1057
    end
1058

1059
    publish :function => :GetModified, :type => "boolean ()"
1✔
1060
    publish :function => :SetModified, :type => "void ()"
1✔
1061
    publish :variable => :modified, :type => "boolean"
1✔
1062
    publish :variable => :proposal_valid, :type => "boolean"
1✔
1063
    publish :variable => :propose_called, :type => "boolean"
1✔
1064
    publish :function => :total_memory, :type => "integer ()"
1✔
1065
    publish :variable => :crashkernel_list_ranges, :type => "boolean"
1✔
1066
    publish :variable => :kdump_packages, :type => "list <string>"
1✔
1067
    publish :variable => :crashkernel_param, :type => "boolean"
1✔
1068
    publish :variable => :add_crashkernel_param, :type => "boolean"
1✔
1069
    publish :variable => :allocated_memory, :type => "map"
1✔
1070
    publish :function => :memory_limits, :type => "map ()"
1✔
1071
    publish :variable => :import_called, :type => "boolean"
1✔
1072
    publish :variable => :write_only, :type => "boolean"
1✔
1073
    publish :variable => :AbortFunction, :type => "boolean ()"
1✔
1074
    publish :variable => :DEFAULT_CONFIG, :type => "map <string, string>"
1✔
1075
    publish :variable => :KDUMP_SETTINGS, :type => "map <string, string>"
1✔
1076
    publish :function => :Abort, :type => "boolean ()"
1✔
1077
    publish :function => :Read, :type => "boolean ()"
1✔
1078
    publish :function => :Update, :type => "boolean ()"
1✔
1079
    publish :function => :Write, :type => "boolean ()"
1✔
1080
    publish :function => :CheckPackages, :type => "void ()"
1✔
1081
    publish :function => :Propose, :type => "void ()"
1✔
1082
    publish :function => :Summary, :type => "list <string> ()"
1✔
1083
    publish :function => :Export, :type => "map ()"
1✔
1084
    publish :function => :Import, :type => "boolean (map)"
1✔
1085

1086
    # Offer this to ensure backward compatibility
1087
    def allocated_memory=(memory)
1✔
1088
      @allocated_memory = if memory.is_a?(::String)
10✔
1089
        if memory.empty?
3✔
1090
          {}
1✔
1091
        else
1092
          { low: memory }
2✔
1093
        end
1094
      else
1095
        memory
7✔
1096
      end
1097
    end
1098

1099
  private
1✔
1100

1101
    # Returns unified directory name with leading and ending "/"
1102
    # for exact matching
1103
    def format_dirname(dirname)
1✔
1104
      "/#{dirname}/".gsub(/\/+/, "/")
21✔
1105
    end
1106

1107
    # get allocated memory from the set of values of the crashkernel option
1108
    #
1109
    # each value can be a set of ranges (first range will be taken) or a
1110
    # concrete value for high or low memory
1111
    # syntax for ranges: 64M@16M or 128M-:64M@16M [(reserved_memory*2)-:reserved_memory]
1112
    # syntax for concrete value: 64M or 64M,high or 64M,low
1113
    #
1114
    #  @param crash_values [Array<string>] list of values
1115
    #  @return [Hash] values of allocated memory ({low: "64", high: "16"})
1116
    def get_allocated_memory(crash_values)
1✔
1117
      result = {}
52✔
1118
      crash_values.each do |crash_value|
52✔
1119
        pieces = crash_value.split(",")
62✔
1120

1121
        if pieces.last =~ /^(low|high)$/i
62✔
1122
          key = pieces.last.downcase.to_sym
28✔
1123
          @crashkernel_list_ranges ||= (pieces.size > 2)
28✔
1124
        else
1125
          key = :low
34✔
1126
          @crashkernel_list_ranges ||= (pieces.size > 1)
34✔
1127
        end
1128
        # Skip everything but the first occurrence
1129
        if result[key]
62✔
1130
          @crashkernel_list_ranges = true
4✔
1131
          next
4✔
1132
        end
1133

1134
        range = pieces.first
58✔
1135
        Builtins.y2milestone("The 1st range from crashkernel is %1", range)
58✔
1136
        value = range.split(":").last.split("M").first
58✔
1137
        result[key] = value
58✔
1138
      end
1139
      Builtins.y2milestone("Allocated memory is %1", result)
52✔
1140
      result
52✔
1141
    end
1142

1143
    # Build crashkernel values from allocated memory
1144
    #
1145
    # @return [Array<String>] list of values of crashkernel
1146
    def crash_kernel_values
1✔
1147
      # If the current values include "nasty" things and the user has not
1148
      # overriden the value of @crashkernel_list_ranges to autorize the
1149
      # modification.
1150
      # The old value (ensuring the Array format) will be returned.
1151
      if @crashkernel_list_ranges
15✔
1152
        return Array(@crashkernel_param_values.to_s) if @crashkernel_param_values.is_a?(Symbol)
2✔
1153

1154
        return Array(@crashkernel_param_values.dup)
2✔
1155
      end
1156

1157
      result = []
13✔
1158
      if @KDUMP_SETTINGS["KDUMP_AUTO_RESIZE"] == "yes"
13✔
1159
        maxsize = total_memory / 2
×
1160
        if high_memory_supported?
×
1161
          low = memory_limits[:default_low]
×
1162
          high = memory_limits[:max_high]
×
1163
          high = (maxsize - low.to_i).to_s if high.to_i > maxsize
×
1164
        else
1165
          high = memory_limits[:min_high]
×
1166
          low = memory_limits[:max_low]
×
1167
          low = maxsize.to_s if low.to_i > maxsize
×
1168
        end
1169
      else
1170
        high = @allocated_memory[:high]
13✔
1171
        low = @allocated_memory[:low]
13✔
1172
      end
1173
      result << "#{high}M,high" if high && high.to_i != 0
13✔
1174
      # Add the ',low' suffix only there is a ',high' one
1175
      result << (result.empty? ? "#{low}M" : "#{low}M,low") if low && low.to_i != 0
13✔
1176

1177
      log.info "built crashkernel values are #{result}"
13✔
1178

1179
      result
13✔
1180
    end
1181

1182
    def crash_xen_kernel_values
1✔
1183
      # If the current values include "nasty" things and the user has not
1184
      # overriden the value of @crashkernel_list_ranges to autorize the
1185
      # modification.
1186
      # The old value (ensuring the Array format) will be returned.
1187
      if @crashkernel_list_ranges
18✔
1188
        if @crashkernel_xen_param_values.is_a?(Symbol)
2✔
1189
          return Array(@crashkernel_xen_param_values.to_s)
×
1190
        else
1191
          return Array(@crashkernel_xen_param_values.dup)
2✔
1192
        end
1193
      end
1194

1195
      result = []
16✔
1196
      if @KDUMP_SETTINGS["KDUMP_AUTO_RESIZE"] == "yes"
16✔
1197
        high = memory_limits[:default_high]
×
1198
        low = memory_limits[:default_low]
×
1199
      else
1200
        high = @allocated_memory[:high]
16✔
1201
        low = @allocated_memory[:low]
16✔
1202
      end
1203
      sum = 0
16✔
1204
      sum += low.to_i if low
16✔
1205
      sum += high.to_i if high
16✔
1206

1207
      result << "#{sum}M\\<4G" if sum != 0
16✔
1208

1209
      log.info "built xen crashkernel values are #{result}"
16✔
1210

1211
      result
16✔
1212
    end
1213

1214
    # Removes offsets from all the crashkernel values
1215
    #
1216
    # Beware: not functional, it modifies the passed argument
1217
    #
1218
    # @param values [Array,Symbol] list of values or one of the special values
1219
    #       returned by Bootloader.kernel_param
1220
    def remove_offsets!(values)
1✔
1221
      # It could also be :missing or :present
1222
      if values.is_a?(Array)
4✔
1223
        values.map! do |value|
4✔
1224
          pieces = value.split("@")
4✔
1225
          if pieces.size > 1
4✔
1226
            Builtins.y2milestone("Delete offset crashkernel value: %1", value)
2✔
1227
          end
1228
          pieces.first
4✔
1229
        end
1230
      end
1231
    end
1232

1233
    def write_fadump_boot_param
1✔
1234
      if fadump_supported?
23✔
1235
        # If fdump is selected and we want to enable kdump
1236
        value = "on" if using_fadump? && @add_crashkernel_param
×
1237
        Bootloader.modify_kernel_params(:common, :recovery, "fadump" => value)
×
1238
        Bootloader.Write unless Yast::Stage.initial # do mass write in installation to speed up
×
1239
      end
1240
    end
1241
  end
1242

1243
  Kdump = KdumpClass.new
1✔
1244
  Kdump.main
1✔
1245
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc