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

yast / yast-kdump / 17854297813

19 Sep 2025 09:25AM UTC coverage: 39.717% (+0.02%) from 39.696%
17854297813

push

github

schubi2
fixed syntax

1 of 1 new or added line in 1 file covered. (100.0%)

28 existing lines in 1 file now uncovered.

730 of 1838 relevant lines covered (39.72%)

3.94 hits per line

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

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

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

33
require "shellwords"
1✔
34

35
module Yast
1✔
36
  class KdumpClass < Module
1✔
37
    include Yast::Logger
1✔
38

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

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

49
    def main
1✔
50
      textdomain "kdump"
1✔
51

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

71
      reset
1✔
72
    end
73

74
    def reset
1✔
75
      # Data was modified?
76
      @modified = false
91✔
77

78
      # kdump config file
79

80
      @kdump_file = "/etc/sysconfig/kdump"
91✔
81

82
      @proposal_valid = false
91✔
83

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

91
      #  list of packages for installation
92
      @kdump_packages = []
91✔
93

94
      # Boolean option indicates kernel parameter
95
      # "crashkernel"
96
      #
97
      # boolean true if kernel parameter is set
98
      @crashkernel_param = false
91✔
99

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

108
      # array values of kernel parameter for Xen hypervisor
109
      # see @crashkernel_param_values for details
110
      @crashkernel_xen_param_values = []
91✔
111

112
      # Boolean option indicates add kernel param
113
      # "crashkernel"
114
      #
115
      # boolean true if kernel parameter will be add
116
      @add_crashkernel_param = false
91✔
117

118
      # Set of values (high and low) for allocation of memory for boot param
119
      # "crashkernel"
120
      #
121
      # hash with up to two keys (:low and :high) and string values
122
      @allocated_memory = {}
91✔
123

124
      # Boolean option indicates that Import()
125
      # was called and data was proposed
126
      #
127
      # boolean true if import was called with data
128

129
      @import_called = false
91✔
130

131
      # Write only, used during autoinstallation/autoupgrade.
132
      # Don't run services and SuSEconfig, it's all done at one place.
133
      @write_only = false
91✔
134

135
      # Abort function
136
      # return boolean return true if abort
137
      @AbortFunction = nil
91✔
138

139
      # map of deafult values for options in UI
140
      #
141
      # global map <string, string >
142

143
      @DEFAULT_CONFIG = {
144
        "KDUMP_KERNELVER"          => "",
91✔
145
        "KDUMP_CPUS"               => "32",
146
        "KDUMP_COMMANDLINE"        => "",
147
        "KDUMP_COMMANDLINE_APPEND" => "",
148
        "KDUMP_AUTO_RESIZE"        => "false",
149
        "KEXEC_OPTIONS"            => "",
150
        "KDUMP_IMMEDIATE_REBOOT"   => "true",
151
        "KDUMP_TRANSFER"           => "",
152
        "KDUMP_SAVEDIR"            => "file:///var/crash",
153
        "KDUMP_KEEP_OLD_DUMPS"     => "0",
154
        "KDUMP_FREE_DISK_SIZE"     => "64",
155
        "KDUMP_VERBOSE"            => "0",
156
        "KDUMP_DUMPLEVEL"          => "31",
157
        "KDUMP_DUMPFORMAT"         => "compressed",
158
        "KDUMP_CONTINUE_ON_ERROR"  => "true",
159
        "KDUMP_REQUIRED_PROGRAMS"  => "",
160
        "KDUMP_PRESCRIPT"          => "",
161
        "KDUMP_POSTSCRIPT"         => "",
162
        "KDUMP_NETCONFIG"          => "auto",
163
        "KDUMP_NET_TIMEOUT"        => "30",
164
        "KDUMP_SMTP_SERVER"        => "",
165
        "KDUMP_SMTP_USER"          => "",
166
        "KDUMP_SMTP_PASSWORD"      => "",
167
        "KDUMP_NOTIFICATION_TO"    => "",
168
        "KDUMP_NOTIFICATION_CC"    => "",
169
        "KDUMP_HOST_KEY"           => ""
170
      }
171

172
      # map <string, string > of kdump settings
173
      #
174
      @KDUMP_SETTINGS = {}
91✔
175

176
      # initial kdump settings replaced in Read function
177
      @initial_kdump_settings = deep_copy(@KDUMP_SETTINGS)
91✔
178
    end
179

180
    # Abort function
181
    # @return [Boolean] return true if abort
182
    def Abort
1✔
183
      return @AbortFunction.call == true unless @AbortFunction.nil?
×
184

185
      false
×
186
    end
187

188
    # Data was modified?
189
    # @return true if modified
190
    def GetModified
1✔
191
      Builtins.y2debug("modified=%1", @modified)
1✔
192
      @modified
1✔
193
    end
194

195
    # Set data was modified
196
    def SetModified
1✔
197
      @modified = true
1✔
198
      Builtins.y2debug("modified=%1", @modified)
1✔
199

200
      nil
1✔
201
    end
202

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

218
      unless FileUtils.Exists("/bin/chmod")
×
219
        Builtins.y2error("tool: /bin/chmod not found")
×
220
        return false
×
221
      end
222

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

226
      if Ops.get_integer(cmd_out, "exit", -1) != 0
×
227
        Builtins.y2error("Command >%1< returned %2", cmd, cmd_out)
×
228
        return false
×
229
      end
230
      Builtins.y2milestone("Command: %1 finish successful.", cmd)
×
231
      true
×
232
    end
233

234
    # Function check if KDUMP_SAVEDIR or
235
    # KDUMP_SMTP_PASSWORD include password
236
    #
237
    # @return [Boolean] true if inlude password
238

239
    def checkPassword
1✔
240
      return true if Ops.get(@KDUMP_SETTINGS, "KDUMP_SMTP_PASSWORD", "") != ""
×
241

242
      if Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "").include?("file") ||
×
243
          Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "").include?("nfs") ||
244
          Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "") == ""
245
        return false
×
246
      end
247

248
      return false unless Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "").include?("@")
×
249

250
      temp = Builtins.splitstring(
×
251
        Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", ""),
252
        "@"
253
      )
254
      position = Builtins.findlastof(Ops.get(temp, 0, ""), ":")
×
255
      return false if position.nil?
×
256

257
      # if there is 2 times ":" -> it means that password is defined
258
      # for example cifs://login:password@server....
259
      position > 6
×
260
    end
261

262
    # Read current kdump configuration
263
    #
264
    # read kernel parameter "crashkernel"
265
    #  @return [Boolean] successfull
266
    def ReadKdumpKernelParam
1✔
267
      result = Bootloader.kernel_param(:common, "crashkernel")
51✔
268
      xen_result = Bootloader.kernel_param(:xen_host, "crashkernel")
51✔
269
      # result could be [String,Array,:missing,:present]
270
      # String   - the value of the only occurrence
271
      # Array    - the values of the multiple occurrences
272
      # :missing - crashkernel is missed
273
      # :present - crashkernel is defined but no value is available
274

275
      if result == :missing
51✔
276
        @crashkernel_param = false
5✔
277
        @add_crashkernel_param = false
5✔
278
      else
279
        @crashkernel_param = true
46✔
280
        @add_crashkernel_param = true
46✔
281
      end
282

283
      if [:missing, :present].include?(result)
51✔
284
        @crashkernel_param_values = result
8✔
285
      else
286
        # Let's make sure it's an array
287
        # filtering nils and empty entries bnc#991140
288
        @crashkernel_param_values = Array(result).compact.reject(&:empty?)
43✔
289
        # Read the current value only if crashkernel parameter is set.
290
        # (bnc#887901)
291
        @allocated_memory = get_allocated_memory(@crashkernel_param_values)
43✔
292
      end
293

294
      @crashkernel_xen_param_values = if [:missing, :present].include?(xen_result)
51✔
295
        xen_result
8✔
296
      else
297
        # Let's make sure it's an array
298
        # filtering nils and empty entries bnc#991140
299
        Array(xen_result).compact.reject(&:empty?)
43✔
300
      end
301

302
      true
51✔
303
    end
304

305
    # Returns the KdumpSystem instance
306
    def system
1✔
307
      @system ||= Yast::KdumpSystem.new
×
308
    end
309

310
    def write_temporary_config_file
1✔
311
      SCR.RegisterAgent(TEMPORARY_CONFIG_PATH,
×
312
        term(:ag_ini,
313
          term(:SysConfigFile, TEMPORARY_CONFIG_FILE)))
314
      WriteKdumpSettingsTo(TEMPORARY_CONFIG_PATH, TEMPORARY_CONFIG_FILE)
×
315
      SCR.UnregisterAgent(TEMPORARY_CONFIG_PATH)
×
316
    end
317

318
    # Return the Kdump calibrator instance
319
    #
320
    # @return [Yast::KdumpCalibrator] Calibrator instance
321
    def calibrator
1✔
322
      return @calibrator unless @calibrator.nil?
82✔
323

324
      if Mode.normal
1✔
325
        @calibrator = Yast::KdumpCalibrator.new
1✔
326
      else
327
        write_temporary_config_file
×
328
        @calibrator = Yast::KdumpCalibrator.new(TEMPORARY_CONFIG_FILE)
×
329
      end
330
    end
331

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

345
    # Propose reserved/allocated memory
346
    # Store the result as a hash to @allocated_memory
347
    # @return [Boolean] true, always successful
348
    def ProposeAllocatedMemory
1✔
349
      # only propose once
350
      return true unless @allocated_memory.empty?
10✔
351

352
      @allocated_memory = { low: calibrator.default_low.to_s, high: calibrator.default_high.to_s }
8✔
353
      Builtins.y2milestone(
8✔
354
        "[kdump] allocated memory if not set in \"crashkernel\" param: %1",
355
        @allocated_memory
356
      )
357
      true
8✔
358
    end
359

360
    # Returns total size of physical memory in MiB
361
    def total_memory
1✔
362
      calibrator.total_memory
1✔
363
    end
364

365
    def log_settings_censoring_passwords(message)
1✔
366
      debug_KDUMP_SETTINGS = deep_copy(@KDUMP_SETTINGS)
2✔
367
      debug_KDUMP_SETTINGS["KDUMP_SAVEDIR"]       = "********"
2✔
368
      debug_KDUMP_SETTINGS["KDUMP_SMTP_PASSWORD"] = "********"
2✔
369

370
      log.info "-------------KDUMP_SETTINGS-------------------"
2✔
371
      log.info "#{message}; here with censored passwords: #{debug_KDUMP_SETTINGS}"
2✔
372
      log.info "---------------------------------------------"
2✔
373
    end
374

375
    # Read current kdump configuration
376
    #
377
    #  @return [Boolean] successful
378
    def ReadKdumpSettings
1✔
379
      @KDUMP_SETTINGS = deep_copy(@DEFAULT_CONFIG)
2✔
380
      SCR.Dir(path(".sysconfig.kdump")).each do |key|
2✔
381
        val = Convert.to_string(
×
382
          SCR.Read(path(".sysconfig.kdump") + key)
383
        )
384
        @KDUMP_SETTINGS[key] = val
×
385
      end
386

387
      log_settings_censoring_passwords("kdump configuration has been read")
2✔
388

389
      @initial_kdump_settings = deep_copy(@KDUMP_SETTINGS)
2✔
390

391
      true
2✔
392
    end
393

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

409
    # @param update_command [String] a command for .target.bash
410
    # @return [Boolean] whether successful
411
    def update_initrd_with(update_command)
1✔
412
      update_logfile = File.join(Directory.logdir, "y2logmkinitrd")
×
413

414
      run_command = update_command + " >> #{update_logfile.shellescape} 2>&1"
×
415
      y2milestone("Running command: #{run_command}")
×
416
      ret = SCR.Execute(path(".target.bash"), run_command)
×
417

418
      if ret != 0
×
419
        y2error("Error updating initrd, see #{update_logfile} or call #{update_command} manually")
×
420
        Report.Error(format(_(
×
421
          "Error updating initrd while calling '%{cmd}'.\n" \
422
          "See %{log} for details."
423
        ), :cmd => update_command, :log => update_logfile))
424
        return false
×
425
      end
426

427
      true
×
428
    end
429

430
    # Writes a file in the /etc/sysconfig/kdump format
431
    def WriteKdumpSettingsTo(scr_path, file_name)
1✔
432
      log_settings_censoring_passwords("kdump configuration for writing")
×
433

434
      @KDUMP_SETTINGS.each do |option_key, option_val|
×
435
        SCR.Write(scr_path + option_key, option_val)
×
436
      end
437
      SCR.Write(scr_path, nil)
×
438

439
      if checkPassword
×
440
        Chmod(file_name, "600")
×
441
      else
442
        Chmod(file_name, "644")
×
443
      end
444
    end
445

446
    # Write current kdump configuration
447
    #
448
    #  @return [Boolean] successful
449
    def WriteKdumpSettings
1✔
450
      WriteKdumpSettingsTo(path(".sysconfig.kdump"), @kdump_file)
×
451

452
      update_initrd
×
453
    end
454

455
    # Write kdump boot arguments - crashkernel and fadump
456
    # set kdump start at boot
457
    #
458
    #  @return [Boolean] successfull
459
    def WriteKdumpBootParameter
1✔
460
      reboot_needed = using_fadump_changed?
23✔
461

462
      # First, write or remove the fadump param if needed
463
      write_fadump_boot_param
23✔
464

465
      # Then, do the same for the crashkernel param
466
      #
467
      # If we need to add crashkernel param
468
      if @add_crashkernel_param
23✔
469
        if Mode.autoinst || Mode.autoupgrade
19✔
470
          # Use the value(s) read by import
471
          crash_values = @crashkernel_param_values
12✔
472
          crash_xen_values = @crashkernel_xen_param_values
12✔
473
          # Always write the value
474
          skip_crash_values = false
12✔
475
        else
476
          # Calculate the param values based on @allocated_memory
477
          crash_values = crash_kernel_values
7✔
478
          crash_xen_values = crash_xen_kernel_values
7✔
479
          remove_offsets!(crash_values) if Mode.update
7✔
480
          remove_offsets!(crash_xen_values) if Mode.update
7✔
481
          # Skip writing of param if it's already set to the desired values
482
          skip_crash_values = @crashkernel_param && @crashkernel_param_values == crash_values
7✔
483
          skip_crash_values &&= @crashkernel_xen_param_values && @crashkernel_xen_param_values == crash_xen_values
7✔
484
        end
485

486
        if skip_crash_values
19✔
487
          # start kdump at boot
488
          Service.Enable(KDUMP_SERVICE_NAME)
2✔
489
          Service.Restart(KDUMP_SERVICE_NAME) if Service.active?(KDUMP_SERVICE_NAME)
2✔
490
        else
491
          Bootloader.modify_kernel_params(:common, :recovery, "crashkernel" => crash_values)
17✔
492
          Bootloader.modify_kernel_params(:xen_host, "crashkernel" => crash_xen_values)
17✔
493
          # do mass write in installation to speed up, so skip this one
494
          old_progress = Progress.set(false)
17✔
495
          Bootloader.Write
17✔
496
          Progress.set(old_progress)
17✔
497
          Builtins.y2milestone(
17✔
498
            "[kdump] (WriteKdumpBootParameter) adding crashkernel options with values: %1",
499
            crash_values
500
          )
501
          Builtins.y2milestone(
17✔
502
            "[kdump] (WriteKdumpBootParameter) adding xen crashkernel options with values: %1",
503
            crash_xen_values
504
          )
505
          reboot_needed = true
17✔
506
          Service.Enable(KDUMP_SERVICE_NAME)
17✔
507
        end
508
      else
509
        # If we don't need the param but it is there
510
        if @crashkernel_param
4✔
511
          # delete crashkernel parameter from bootloader
512
          Bootloader.modify_kernel_params(:common, :xen_guest, :recovery, :xen_host, "crashkernel" => :missing)
1✔
513
          old_progress = Progress.set(false)
1✔
514
          Bootloader.Write
1✔
515
          Progress.set(old_progress)
1✔
516
          reboot_needed = true
1✔
517
        end
518
        Service.Disable(KDUMP_SERVICE_NAME)
4✔
519
        Service.Stop(KDUMP_SERVICE_NAME) if Service.active?(KDUMP_SERVICE_NAME)
4✔
520
      end
521

522
      if reboot_needed && Mode.normal && !Mode.commandline
23✔
523
        Popup.Message(_("To apply changes a reboot is necessary."))
18✔
524
      end
525

526
      true
23✔
527
    end
528

529
    # Read all kdump settings
530
    # @return true on success
531
    def Read
1✔
532
      # Kdump read dialog caption
533
      caption = _("Initializing kdump Configuration")
×
534
      steps = 4
×
535

536
      Progress.New(
×
537
        caption,
538
        " ",
539
        steps,
540
        [
541
          # Progress stage 1/4
542
          _("Reading the config file..."),
543
          # Progress stage 3/4
544
          _("Reading kernel boot options..."),
545
          # Progress stage 4/4
546
          _("Calculating memory limits...")
547
        ],
548
        [
549
          # Progress step 1/4
550
          _("Reading the config file..."),
551
          # Progress step 2/4
552
          _("Reading partitions of disks..."),
553
          # Progress finished 3/4
554
          _("Reading available memory and calibrating usage..."),
555
          # Progress finished 4/4
556
          Message.Finished
557
        ],
558
        ""
559
      )
560

561
      # read database
562
      return false if Abort()
×
563

564
      Progress.NextStage
×
565
      # Error message
566
      Report.Error(_("Cannot read config file /etc/sysconfig/kdump")) unless ReadKdumpSettings()
×
567

568
      # read another database
569
      return false if Abort()
×
570

571
      Progress.NextStep
×
572
      # Error message
573
      Report.Error(_("Cannot read kernel boot options.")) unless ReadKdumpKernelParam()
×
574

575
      # read another database
576
      return false if Abort()
×
577

578
      Progress.NextStep
×
579
      ProposeAllocatedMemory()
×
580
      # Error message
581
      Report.Error(_("Cannot read available memory.")) if total_memory.zero?
×
582

583
      return false if Abort()
×
584

585
      # Progress finished
586
      Progress.NextStage
×
587

588
      return false if Abort()
×
589

590
      @modified = false
×
591
      true
×
592
    end
593

594
    # Update crashkernel argument during update of OS
595
    # @return true on success
596

597
    def Update
1✔
598
      Builtins.y2milestone("Update kdump settings")
2✔
599
      ReadKdumpKernelParam() unless Mode.autoupgrade
2✔
600
      WriteKdumpBootParameter()
2✔
601
      true
2✔
602
    end
603

604
    # Write all kdump settings
605
    # @return true on success
606
    def Write
1✔
607
      # Kdump read dialog caption
608
      caption = _("Saving kdump Configuration")
×
609

610
      # number of stages
611
      steps = 2
×
612
      if (Mode.installation || Mode.autoinst) && !@add_crashkernel_param
×
613
        Builtins.y2milestone(
×
614
          "Skip writing of configuration for kdump during installation"
615
        )
616
        return true
×
617
      end
618

619
      # We do not set help text here, because it was set outside
620
      Progress.New(
×
621
        caption,
622
        " ",
623
        steps,
624
        [
625
          # Progress stage 1/2
626
          _("Write the settings"),
627
          # Progress stage 2/2
628
          _("Update boot options")
629
        ],
630
        [
631
          # Progress step 1/2
632
          _("Writing the settings..."),
633
          # Progress step 2/2
634
          _("Updating boot options..."),
635
          # Progress finished
636
          _("Finished")
637
        ],
638
        ""
639
      )
640

641
      # write settings
642
      return false if Abort()
×
643

644
      Progress.NextStage
×
645
      # Error message
646
      unless WriteKdumpSettings()
×
647
        Report.Error(_("Cannot write settings."))
×
648
        return false
×
649
      end
650

651
      # write/delete bootloader options for kernel - "crashkernel" and "fadump"
652
      return false if Abort()
×
653

654
      Progress.NextStage
×
655
      # Error message
656
      unless WriteKdumpBootParameter()
×
657
        Report.Error(_("Adding crashkernel parameter to bootloader fault."))
×
658
      end
659

660
      return false if Abort()
×
661

662
      # Progress finished
663
      Progress.NextStage
×
664

665
      return false if Abort()
×
666

667
      true
×
668
    end
669

670
    # Adding necessary packages for installation
671
    #
672

673
    def AddPackages
1✔
674
      return unless Mode.installation
1✔
675

676
      @kdump_packages.concat KDUMP_PACKAGES
×
677
    end
678

679
    # Proposes default state of kdump (enabled/disabled)
680
    #
681
    # @return [Boolean] the default proposed state
682

683
    def ProposeCrashkernelParam
1✔
684
      # proposing disabled kdump if product wants it (bsc#1071242)
685
      if !ProductFeatures.GetBooleanFeature("globals", "enable_kdump")
5✔
686
        log.info "Kdump disabled in control file"
1✔
687
        false
1✔
688
      # proposing disabled kdump if PC has less than 1024MB RAM
689
      elsif total_memory < 1024
4✔
690
        log.info "not enough memory - kdump proposed as disabled"
1✔
691
        false
1✔
692
      # proposing disabled kdump on aarch64 (bsc#989321) - kdump not implemented
693
      elsif Arch.aarch64
3✔
694
        log.info "aarch64 - kdump proposed as disabled"
1✔
695
        false
1✔
696
      else
697
        true
2✔
698
      end
699
    end
700

701
    # Propose global variables once...
702
    # after that remember user settings
703

704
    def ProposeGlobalVars
1✔
705
      # Settings have not been imported by AutoYaST and have not already
706
      # been changed. (bnc#930950, bnc#995750, bnc#890719).
707
      if !@modified && !@import_called
1✔
708
        # added default settings
UNCOV
709
        @KDUMP_SETTINGS = deep_copy(@DEFAULT_CONFIG)
×
UNCOV
710
        @add_crashkernel_param = ProposeCrashkernelParam()
×
UNCOV
711
        @crashkernel_param = false
×
712
      end
713

714
      nil
1✔
715
    end
716

717
    # Check if user enabled kdump
718
    # if no deselect packages for installing
719
    # if yes add necessary packages for installation
720
    def CheckPackages
1✔
721
      # remove duplicates
722
      @kdump_packages.uniq!
1✔
723
      if @add_crashkernel_param
1✔
724
        Builtins.y2milestone(
1✔
725
          "select packages for installation: %1",
726
          @kdump_packages
727
        )
728
        @kdump_packages.each do |p|
1✔
UNCOV
729
          PackagesProposal.AddResolvables("yast2-kdump", :package, [p])
×
730
        end
731
        unless @kdump_packages.empty?
1✔
UNCOV
732
          Builtins.y2milestone(
×
733
            "Selected kdump packages for installation: %1",
734
            @kdump_packages
735
          )
736
        end
737
      else
UNCOV
738
        Builtins.y2milestone(
×
739
          "deselect packages for installation: %1",
740
          @kdump_packages
741
        )
742
        @kdump_packages.each do |p|
×
UNCOV
743
          PackagesProposal.RemoveResolvables("yast2-kdump", :package, [p])
×
744
        end
UNCOV
745
        unless @kdump_packages.empty?
×
746
          Builtins.y2milestone(
×
747
            "Deselected kdump packages for installation: %1",
748
            @kdump_packages
749
          )
750
        end
751
      end
752

753
      nil
1✔
754
    end
755

756
    # Propose all kdump settings
757
    #
758
    def Propose
1✔
759
      Builtins.y2milestone("Proposing new settings of kdump")
1✔
760
      # set default values for global variables
761
      ProposeGlobalVars()
1✔
762
      # check available memory and execute the calibrator
763
      ProposeAllocatedMemory()
1✔
764
      # add packages for installation
765
      AddPackages()
1✔
766
      # select packages for installation
767
      CheckPackages()
1✔
768

769
      nil
1✔
770
    end
771

772
    # Create a textual summary
773
    # @return summary of the current configuration
774
    def Summary
1✔
775
      result = []
1✔
776
      result = Builtins.add(
1✔
777
        result,
778
        Builtins.sformat(
779
          _("Kdump status: %1"),
780
          @add_crashkernel_param ? _("enabled") : _("disabled")
1✔
781
        )
782
      )
783
      if @add_crashkernel_param
1✔
UNCOV
784
        result = Builtins.add(
×
785
          result,
786
          Builtins.sformat(
787
            _("Value(s) of crashkernel option: %1"),
788
            crash_kernel_values.join(" ")
789
          )
790
        )
UNCOV
791
        result = Builtins.add(
×
792
          result,
793
          Builtins.sformat(
794
            _("Dump format: %1"),
795
            Ops.get(@KDUMP_SETTINGS, "KDUMP_DUMPFORMAT", "")
796
          )
797
        )
UNCOV
798
        result = Builtins.add(
×
799
          result,
800
          Builtins.sformat(
801
            _("Target of dumps: %1"),
802
            Ops.get(@KDUMP_SETTINGS, "KDUMP_SAVEDIR", "")
803
          )
804
        )
UNCOV
805
        result = Builtins.add(
×
806
          result,
807
          Builtins.sformat(
808
            _("Number of dumps: %1"),
809
            Ops.get(@KDUMP_SETTINGS, "KDUMP_KEEP_OLD_DUMPS", "")
810
          )
811
        )
812
      end
813
      deep_copy(result)
1✔
814
    end
815

816
    # Returns available space (in bytes) for Kernel dump according to KDUMP_SAVEDIR option
817
    # only local space is evaluated (starts with file://)
818
    #
819
    # @return [Integer] free space in bytes or nil if filesystem is not local or no
820
    #                   packages proposal is made yet
821
    def free_space_for_dump_b
1✔
822
      kdump_savedir = @KDUMP_SETTINGS.fetch("KDUMP_SAVEDIR", "file:///var/log/dump").dup
8✔
823
      log.info "Using savedir #{kdump_savedir}"
8✔
824

825
      if kdump_savedir.start_with?("/")
8✔
UNCOV
826
        log.warn "Using old format"
×
827
      elsif kdump_savedir.start_with?("file://")
8✔
828
        kdump_savedir.sub!(/file:\/\//, "")
6✔
829
      else
830
        log.info "KDUMP_SAVEDIR #{kdump_savedir.inspect} is not local"
2✔
831
        return nil
2✔
832
      end
833

834
      # unified format of directory
835
      kdump_savedir = format_dirname(kdump_savedir)
6✔
836

837
      partitions_info = SpaceCalculation.GetPartitionInfo()
6✔
838
      if partitions_info.empty?
6✔
839
        log.warn "No partitions info available"
1✔
840
        return nil
1✔
841
      end
842

843
      log.info "Disk usage: #{partitions_info}"
5✔
844
      # Create a hash of partitions and their free space { partition => free_space, ... }
845
      # "name" usually does not start with "/", but does so for root filesystem
846
      # File.join ensures that paths do not contain dulplicit "/" characters
847
      partitions_info = partitions_info.map do |partition|
5✔
848
        { format_dirname(partition["name"]) => partition["free"] }
15✔
849
      end.inject(:merge)
850

851
      # All partitions matching KDUMP_SAVEDIR
852
      matching_partitions = partitions_info.select do |partition, _space|
5✔
853
        kdump_savedir.start_with?(partition)
15✔
854
      end
855

856
      # The longest match
857
      partition = matching_partitions.keys.max_by { |partiton| partiton.length }
13✔
858
      free_space = matching_partitions[partition]
5✔
859

860
      if free_space.nil? || !free_space.is_a?(::Integer)
5✔
861
        log.warn "Available space for partition #{partition} not provided (#{free_space.inspect})"
2✔
862
        return nil
2✔
863
      end
864

865
      # packager counts in kB, we need bytes
866
      free_space *= 1024
3✔
867
      log.info "Available space for dump: #{free_space} bytes in #{partition} directory"
3✔
868

869
      free_space
3✔
870
    end
871

872
    # Returns disk space in bytes requested for kernel dump (as defined in FATE#317488)
873
    #
874
    # @return [Integer] bytes
875
    def space_requested_for_dump_b
1✔
876
      # Total memory is in MB, converting to bytes
877
      (total_memory * (1024**2)) + RESERVED_DISK_SPACE_BUFFER_B
1✔
878
    end
879

880
    # Returns installation proposal warning as part of the MakeProposal map result
881
    # includes 'warning' and 'warning_level' keys
882
    #
883
    # @param returns [Hash] with warnings
884
    def proposal_warning
1✔
885
      return {} unless @add_crashkernel_param
3✔
886

887
      free_space = free_space_for_dump_b
2✔
888
      requested_space = space_requested_for_dump_b
2✔
889

890
      log.info "Free: #{free_space}, requested: #{requested_space}"
2✔
891
      return {} if free_space.nil? || requested_space.nil?
2✔
892

893
      warning = {}
2✔
894
      warning_string = ""
2✔
895

896
      if free_space < requested_space
2✔
897
        # TRANSLATORS: warning message in installation proposal. Do not translate %{requested} and
898
        # %{available} - they are replaced with actual sizes later.
899
        warning_string = format(_(
1✔
900
          "Warning! There might not be enough free space to have kdump enabled. " \
901
          "%{required} required for saving a kernel dump, but only %{available} are available."
902
        ), required: String.FormatSizeWithPrecision(requested_space, 2, true), available: String.FormatSizeWithPrecision(free_space, 2, true))
903
      end
904

905
      unless warning_string.empty?
2✔
906
        warning = {
1✔
907
          "warning_level" => :warning,
908
          "warning"       => "<ul><li>" + warning_string + "</li></ul>"
909
        }
910
      end
911

912
      log.warn warning["warning"] if warning["warning"]
2✔
913
      warning
2✔
914
    end
915

916
    # bnc# 480466 - fix problem with validation autoyast profil
917
    # Function filters keys for autoyast profil
918
    #
919
    # @param map <string, string > KDUMP_SETTINGS
920
    # @return [Hash{String => String}] filtered KDUMP_SETTINGS by DEFAULT_CONFIG
921

922
    def filterExport(settings)
1✔
923
      settings = deep_copy(settings)
1✔
924
      keys = Map.Keys(@DEFAULT_CONFIG)
1✔
925
      Builtins.filter(settings) do |key, _value|
1✔
926
        Builtins.contains(keys, key)
26✔
927
      end
928
    end
929

930
    # Export kdump settings to a map
931
    # @return kdump settings
932
    def Export
1✔
933
      if @add_crashkernel_param
3✔
934
        crash_kernel = crash_kernel_values
1✔
935
        crash_kernel = crash_kernel[0] if crash_kernel.size == 1
1✔
936
        crash_xen_kernel = crash_xen_kernel_values
1✔
937
        crash_xen_kernel = crash_xen_kernel[0] if crash_xen_kernel.size == 1
1✔
938
        out = {
939
          "crash_kernel"     => crash_kernel,
1✔
940
          "crash_xen_kernel" => crash_xen_kernel,
941
          "add_crash_kernel" => true,
942
          "general"          => filterExport(@KDUMP_SETTINGS)
943
        }
944
      else
945
        out = { "add_crash_kernel" => false }
2✔
946
      end
947

948
      Builtins.y2milestone("Kdump exporting settings: %1", out)
3✔
949
      deep_copy(out)
3✔
950
    end
951

952
    # Import settings from a map
953
    # @param [Hash, nil] settings map of kdump settings
954
    # @return [Boolean] true on success
955
    def Import(settings)
1✔
956
      settings ||= {}
16✔
957
      Builtins.y2milestone("Importing settings for kdump #{settings.inspect}")
16✔
958

959
      my_import_map = Ops.get_map(settings, "general", {})
16✔
960
      @DEFAULT_CONFIG.each_pair do |key, def_value|
16✔
961
        value = my_import_map[key]
416✔
962
        @KDUMP_SETTINGS[key] = value.nil? ? def_value : value
416✔
963
      end
964

965
      if settings.key?("crash_kernel")
16✔
966
        # Make sure it's an array
967
        @crashkernel_param_values = Array(settings.fetch("crash_kernel", ""))
9✔
968
        # In order not to overwrite the values by the proposal we will have to set
969
        # according allocated memory too. (bnc#995750)
970
        @allocated_memory = get_allocated_memory(@crashkernel_param_values)
9✔
971
      else
972
        # Taking proposed values (bnc#997448)
973
        ProposeAllocatedMemory()
7✔
974
        # Make sure it's an array
975
        @crashkernel_param_values = Array(crash_kernel_values)
7✔
976
      end
977

978
      @crashkernel_xen_param_values = if settings.key?("crash_xen_kernel")
16✔
979
        # Make sure it's an array
980
        Array(settings.fetch("crash_xen_kernel", ""))
6✔
981
      else
982
        Array(crash_xen_kernel_values)
10✔
983
      end
984

985
      @add_crashkernel_param = if settings.key?("add_crash_kernel")
16✔
986
        settings["add_crash_kernel"]
15✔
987
      else
988
        ProposeCrashkernelParam()
1✔
989
      end
990

991
      if settings.key?("crash_kernel") || settings.key?("add_crash_kernel") ||
16✔
992
          !my_import_map.empty?
993
        @import_called = true
15✔
994
      end
995

996
      true
16✔
997
    end
998

999
    # Sets whether to use FADump (Firmware assisted dump)
1000
    #
1001
    # @param [Boolean] new state
1002
    # @return [Boolean] whether successfully set
1003
    def use_fadump(new_value)
1✔
1004
      # Trying to use fadump on unsupported hardware
1005
      if !fadump_supported? && new_value
7✔
1006
        Builtins.y2milestone("FADump is not supported on this hardware")
1✔
1007
        Report.Error(_("Cannot use Firmware-assisted dump.\nIt is not supported on this hardware."))
1✔
1008
        return false
1✔
1009
      end
1010

1011
      @KDUMP_SETTINGS[FADUMP_KEY] = (new_value ? "true" : "false")
6✔
1012
      true
6✔
1013
    end
1014

1015
    # Returns whether FADump (Firmware assisted dump) is currently in use
1016
    #
1017
    # @return [Boolean] currently in use
1018
    def using_fadump?
1✔
1019
      ["yes", "true", "1"].include?(@KDUMP_SETTINGS[FADUMP_KEY])
3✔
1020
    end
1021

1022
    # Has the using_fadump? been changed?
1023
    #
1024
    # @return [Boolean] whether changed
1025
    def using_fadump_changed?
1✔
1026
      @initial_kdump_settings[FADUMP_KEY] != @KDUMP_SETTINGS[FADUMP_KEY]
25✔
1027
    end
1028

1029
    # Returns whether usage of high memory in the crashkernel bootloader param
1030
    # is supported by the current system
1031
    #
1032
    # @return [Boolean] is supported
1033
    def high_memory_supported?
1✔
UNCOV
1034
      calibrator.high_memory_supported?
×
1035
    end
1036

1037
    # Returns whether usage of fadump is supported by the current system
1038
    #
1039
    # @return [Boolean] is supported
1040
    def fadump_supported?
1✔
1041
      calibrator.fadump_supported?
5✔
1042
    end
1043

1044
    publish :function => :GetModified, :type => "boolean ()"
1✔
1045
    publish :function => :SetModified, :type => "void ()"
1✔
1046
    publish :variable => :modified, :type => "boolean"
1✔
1047
    publish :variable => :proposal_valid, :type => "boolean"
1✔
1048
    publish :function => :total_memory, :type => "integer ()"
1✔
1049
    publish :variable => :crashkernel_list_ranges, :type => "boolean"
1✔
1050
    publish :variable => :kdump_packages, :type => "list <string>"
1✔
1051
    publish :variable => :crashkernel_param, :type => "boolean"
1✔
1052
    publish :variable => :add_crashkernel_param, :type => "boolean"
1✔
1053
    publish :variable => :allocated_memory, :type => "map"
1✔
1054
    publish :function => :memory_limits, :type => "map ()"
1✔
1055
    publish :variable => :import_called, :type => "boolean"
1✔
1056
    publish :variable => :write_only, :type => "boolean"
1✔
1057
    publish :variable => :AbortFunction, :type => "boolean ()"
1✔
1058
    publish :variable => :DEFAULT_CONFIG, :type => "map <string, string>"
1✔
1059
    publish :variable => :KDUMP_SETTINGS, :type => "map <string, string>"
1✔
1060
    publish :function => :Abort, :type => "boolean ()"
1✔
1061
    publish :function => :Read, :type => "boolean ()"
1✔
1062
    publish :function => :Update, :type => "boolean ()"
1✔
1063
    publish :function => :Write, :type => "boolean ()"
1✔
1064
    publish :function => :CheckPackages, :type => "void ()"
1✔
1065
    publish :function => :Propose, :type => "void ()"
1✔
1066
    publish :function => :Summary, :type => "list <string> ()"
1✔
1067
    publish :function => :Export, :type => "map ()"
1✔
1068
    publish :function => :Import, :type => "boolean (map)"
1✔
1069

1070
    # Offer this to ensure backward compatibility
1071
    def allocated_memory=(memory)
1✔
1072
      @allocated_memory = if memory.is_a?(::String)
10✔
1073
        if memory.empty?
3✔
1074
          {}
1✔
1075
        else
1076
          { low: memory }
2✔
1077
        end
1078
      else
1079
        memory
7✔
1080
      end
1081
    end
1082

1083
  private
1✔
1084

1085
    # Returns unified directory name with leading and ending "/"
1086
    # for exact matching
1087
    def format_dirname(dirname)
1✔
1088
      "/#{dirname}/".gsub(/\/+/, "/")
21✔
1089
    end
1090

1091
    # get allocated memory from the set of values of the crashkernel option
1092
    #
1093
    # each value can be a set of ranges (first range will be taken) or a
1094
    # concrete value for high or low memory
1095
    # syntax for ranges: 64M@16M or 128M-:64M@16M [(reserved_memory*2)-:reserved_memory]
1096
    # syntax for concrete value: 64M or 64M,high or 64M,low
1097
    #
1098
    #  @param crash_values [Array<string>] list of values
1099
    #  @return [Hash] values of allocated memory ({low: "64", high: "16"})
1100
    def get_allocated_memory(crash_values)
1✔
1101
      result = {}
52✔
1102
      crash_values.each do |crash_value|
52✔
1103
        pieces = crash_value.split(",")
62✔
1104

1105
        if pieces.last =~ /^(low|high)$/i
62✔
1106
          key = pieces.last.downcase.to_sym
28✔
1107
          @crashkernel_list_ranges ||= (pieces.size > 2)
28✔
1108
        else
1109
          key = :low
34✔
1110
          @crashkernel_list_ranges ||= (pieces.size > 1)
34✔
1111
        end
1112
        # Skip everything but the first occurrence
1113
        if result[key]
62✔
1114
          @crashkernel_list_ranges = true
4✔
1115
          next
4✔
1116
        end
1117

1118
        range = pieces.first
58✔
1119
        Builtins.y2milestone("The 1st range from crashkernel is %1", range)
58✔
1120
        value = range.split(":").last.split("M").first
58✔
1121
        result[key] = value
58✔
1122
      end
1123
      Builtins.y2milestone("Allocated memory is %1", result)
52✔
1124
      result
52✔
1125
    end
1126

1127
    # Build crashkernel values from allocated memory
1128
    #
1129
    # @return [Array<String>] list of values of crashkernel
1130
    def crash_kernel_values
1✔
1131
      # If the current values include "nasty" things and the user has not
1132
      # overriden the value of @crashkernel_list_ranges to autorize the
1133
      # modification.
1134
      # The old value (ensuring the Array format) will be returned.
1135
      if @crashkernel_list_ranges
15✔
1136
        return Array(@crashkernel_param_values.to_s) if @crashkernel_param_values.is_a?(Symbol)
2✔
1137

1138
        return Array(@crashkernel_param_values.dup)
2✔
1139
      end
1140

1141
      result = []
13✔
1142
      if ["yes", "true", "1"].include?(@KDUMP_SETTINGS["KDUMP_AUTO_RESIZE"])
13✔
UNCOV
1143
        maxsize = total_memory / 2
×
UNCOV
1144
        if high_memory_supported?
×
UNCOV
1145
          low = memory_limits[:default_low]
×
UNCOV
1146
          high = memory_limits[:max_high]
×
UNCOV
1147
          high = (maxsize - low.to_i).to_s if high.to_i > maxsize
×
1148
        else
UNCOV
1149
          high = memory_limits[:min_high]
×
UNCOV
1150
          low = memory_limits[:max_low]
×
UNCOV
1151
          low = maxsize.to_s if low.to_i > maxsize
×
1152
        end
1153
      else
1154
        high = @allocated_memory[:high]
13✔
1155
        low = @allocated_memory[:low]
13✔
1156
      end
1157
      result << "#{high}M,high" if high && high.to_i != 0
13✔
1158
      # Add the ',low' suffix only there is a ',high' one
1159
      result << (result.empty? ? "#{low}M" : "#{low}M,low") if low && low.to_i != 0
13✔
1160

1161
      log.info "built crashkernel values are #{result}"
13✔
1162

1163
      result
13✔
1164
    end
1165

1166
    def crash_xen_kernel_values
1✔
1167
      # If the current values include "nasty" things and the user has not
1168
      # overriden the value of @crashkernel_list_ranges to autorize the
1169
      # modification.
1170
      # The old value (ensuring the Array format) will be returned.
1171
      if @crashkernel_list_ranges
18✔
1172
        if @crashkernel_xen_param_values.is_a?(Symbol)
2✔
UNCOV
1173
          return Array(@crashkernel_xen_param_values.to_s)
×
1174
        end
1175

1176
        return Array(@crashkernel_xen_param_values.dup)
2✔
1177
      end
1178

1179
      result = []
16✔
1180
      if ["yes", "true", "1"].include?(@KDUMP_SETTINGS["KDUMP_AUTO_RESIZE"])
16✔
UNCOV
1181
        high = memory_limits[:default_high]
×
1182
        low = memory_limits[:default_low]
×
1183
      else
1184
        high = @allocated_memory[:high]
16✔
1185
        low = @allocated_memory[:low]
16✔
1186
      end
1187
      sum = 0
16✔
1188
      sum += low.to_i if low
16✔
1189
      sum += high.to_i if high
16✔
1190

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

1193
      log.info "built xen crashkernel values are #{result}"
16✔
1194

1195
      result
16✔
1196
    end
1197

1198
    # Removes offsets from all the crashkernel values
1199
    #
1200
    # Beware: not functional, it modifies the passed argument
1201
    #
1202
    # @param values [Array,Symbol] list of values or one of the special values
1203
    #       returned by Bootloader.kernel_param
1204
    def remove_offsets!(values)
1✔
1205
      # It could also be :missing or :present
1206
      return unless values.is_a?(Array)
4✔
1207

1208
      values.map! do |value|
4✔
1209
        pieces = value.split("@")
4✔
1210
        Builtins.y2milestone("Delete offset crashkernel value: %1", value) if pieces.size > 1
4✔
1211
        pieces.first
4✔
1212
      end
1213
    end
1214

1215
    def write_fadump_boot_param
1✔
1216
      return unless fadump_supported?
23✔
1217

1218
      # If fdump is selected and we want to enable kdump
UNCOV
1219
      value = "on" if using_fadump? && @add_crashkernel_param
×
UNCOV
1220
      value ||= :missing
×
UNCOV
1221
      Bootloader.modify_kernel_params(:common, :recovery, "fadump" => value)
×
UNCOV
1222
      Bootloader.Write unless Yast::Stage.initial # do mass write in installation to speed up
×
1223
    end
1224
  end
1225

1226
  Kdump = KdumpClass.new
1✔
1227
  Kdump.main
1✔
1228
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