Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added blkdiscard #134

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 178 additions & 98 deletions basilico.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def dispatch_command(self, cmd: str, args: str) -> (Optional[Callable[[str, str]
"sudo_password": self.sudo_password,
"smartctl": self.get_smartctl,
"queued_smartctl": self.queued_get_smartctl,
"queued_badblocks": self.badblocks,
"queued_erase": self.erase,
"queued_cannolo": self.cannolo,
"queued_sleep": self.sleep,
"queued_umount": self.umount,
Expand Down Expand Up @@ -443,12 +443,10 @@ def remove_one_from_queue(self, _cmd: str, queue_id: str):
break
return None

def badblocks(self, _cmd: str, dev: str):
def erase(self, _cmd: str, dev: str):
go_ahead = self._unswap()
if not go_ahead:
return

self._queued_command.notify_start("Running badblocks")
if TEST_MODE:
final_message = ""
for progress in range(0, 100, 10):
Expand All @@ -463,104 +461,186 @@ def badblocks(self, _cmd: str, dev: str):
completed = True
all_ok = False
else:
custom_env = os.environ.copy()
custom_env["LC_ALL"] = "C"

pipe = subprocess.Popen(
(
"sudo",
"-n",
"badblocks",
"-w",
"-s",
"-p",
"0",
"-t",
"0x00",
"-b",
"4096",
dev,
),
stderr=subprocess.PIPE,
env=custom_env,
) # , stdout=subprocess.PIPE)

percent = 0.0
reading_and_comparing = False
errors = -1
deleting = False
buffer = bytearray()
for char in iter(lambda: pipe.stderr.read(1), b""):
if not self._go:
pipe.kill()
pipe.wait()
print(f"Killed badblocks process {self.get_queued_command().id()}")
self._queued_command.notify_finish_with_error("Process terminated by user.")
return
if char == b"":
if pipe.poll() is not None:
break
elif char == b"\b":
if not deleting:
result = buffer.decode("utf-8")
errors_print = "?"

reading_and_comparing = reading_and_comparing or ("Reading and comparing" in result)

# If other messages are printed, ignore them
i = result.index("% done")
if i >= 0:
# /2 due to the 0x00 test + read & compare
percent = float(result[i - 6 : i]) / 2
if reading_and_comparing:
percent += 50
i = result.index("(", i)
if i >= 0:
# errors_str = result[i+1:].split(")", 1)[0]
errors_str = result[i + 1 :].split(" ", 1)[0]
# The errors are read, write and corruption
errors_str = errors_str.split("/")
errors = 0 # badblocks prints the 3 totals every time
for error in errors_str:
errors += int(error)
errors_print = str(errors)
self._queued_command.notify_percentage(percent, f"{errors_print} errors")
buffer.clear()
deleting = True
# elif char == b'\n':
# # Skip the first lines (total number of blocks)
# buffer.clear()
else:
if deleting:
deleting = False
buffer += char

# TODO: was this needed? Why were we doing it twice?
# pipe.wait()
exitcode = pipe.wait()

if errors <= -1:
all_ok = None
errors_print = "an unknown amount of"
elif errors == 0:
all_ok = True
errors_print = "no"
is_ssd = subprocess.run(f"lsblk -o ROTA {dev}", shell=True).stdout.split("\n")[1].strip() == "1"
if is_ssd:
return self.blkdiscard(_cmd, dev)
else:
all_ok = False
errors_print = str(errors)
final_message = f"Finished with {errors_print} errors"
return self.badblocks(_cmd, dev)

def blkdiscard(self, _cmd: str, dev: str):
import time

# Get the size of the device in GB
result = subprocess.run(
['lsblk', '-bnd', '-o', 'SIZE', dev],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
if result.returncode != 0:
logging.error(f"Failed to get device size: {result.stderr}")
return False

device_size_bytes = int(result.stdout.strip())
device_size_gb = device_size_bytes / (1024 ** 3)

# Estimate total time in seconds (4 seconds per GB)
estimated_total_time = device_size_gb * 4

# Start the blkdiscard process
custom_env = os.environ.copy()
custom_env["LC_ALL"] = "C"
pipe = subprocess.Popen(
[
"sudo",
"-n",
"blkdiscard",
"-z",
"-f",
dev,
],
stderr=subprocess.PIPE,
env=custom_env,
)

# Progress estimation
start_time = time.time()
while pipe.poll() is None:
elapsed_time = time.time() - start_time
percentage = min((elapsed_time / estimated_total_time) * 100, 99.9)
self._queued_command.notify_percentage(percentage)
time.sleep(1)

stderr = pipe.stderr.read().decode("utf-8")
exitcode = pipe.wait()

if exitcode == 0:
completed = True
all_ok = True
self._queued_command.notify_percentage(100)
else:
self._queued_command.notify_error()
self._queued_command.notify_finish_with_error(f"blkdiscard exited with status {exitcode}")
completed = False
all_ok = False

with disks_lock:
update_disks_if_needed(self)
disk_ref = disks[dev]

# noinspection PyBroadException

try:
disk_ref.update_erase(completed, all_ok)
except Exception as e:
final_message = f"Error during upload. {final_message}"
self._queued_command.notify_error(final_message)
logging.warning(
f"[{self._the_id}] Can't update blkdiscard results of {dev} on tarallo",
exc_info=e,
)
self._queued_command.notify_finish(final_message)

if exitcode == 0:
# self._queued_command.notify_finish(final_message)
completed = True
def badblocks(self, _cmd: str, dev: str):
self._queued_command.notify_start("Running badblocks")
custom_env = os.environ.copy()
custom_env["LC_ALL"] = "C"

pipe = subprocess.Popen(
(
"sudo",
"-n",
"badblocks",
"-w",
"-s",
"-p",
"0",
"-t",
"0x00",
"-b",
"4096",
dev,
),
stderr=subprocess.PIPE,
env=custom_env,
) # , stdout=subprocess.PIPE)

percent = 0.0
reading_and_comparing = False
errors = -1
deleting = False
buffer = bytearray()
for char in iter(lambda: pipe.stderr.read(1), b""):
if not self._go:
pipe.kill()
pipe.wait()
print(f"Killed badblocks process {self.get_queued_command().id()}")
self._queued_command.notify_finish_with_error("Process terminated by user.")
return
if char == b"":
if pipe.poll() is not None:
break
elif char == b"\b":
if not deleting:
result = buffer.decode("utf-8")
errors_print = "?"

reading_and_comparing = reading_and_comparing or ("Reading and comparing" in result)

# If other messages are printed, ignore them
i = result.index("% done")
if i >= 0:
# /2 due to the 0x00 test + read & compare
percent = float(result[i - 6 : i]) / 2
if reading_and_comparing:
percent += 50
i = result.index("(", i)
if i >= 0:
# errors_str = result[i+1:].split(")", 1)[0]
errors_str = result[i + 1 :].split(" ", 1)[0]
# The errors are read, write and corruption
errors_str = errors_str.split("/")
errors = 0 # badblocks prints the 3 totals every time
for error in errors_str:
errors += int(error)
errors_print = str(errors)
self._queued_command.notify_percentage(percent, f"{errors_print} errors")
buffer.clear()
deleting = True
# elif char == b'\n':
# # Skip the first lines (total number of blocks)
# buffer.clear()
else:
self._queued_command.notify_error()
final_message += f" and badblocks exited with status {exitcode}"
# self._queued_command.notify_finish(final_message)
completed = False
if deleting:
deleting = False
buffer += char

# TODO: was this needed? Why were we doing it twice?
# pipe.wait()
exitcode = pipe.wait()

if errors <= -1:
all_ok = None
errors_print = "an unknown amount of"
elif errors == 0:
all_ok = True
errors_print = "no"
else:
all_ok = False
errors_print = str(errors)
final_message = f"Finished with {errors_print} errors"

if exitcode == 0:
# self._queued_command.notify_finish(final_message)
completed = True
else:
self._queued_command.notify_error()
final_message += f" and badblocks exited with status {exitcode}"
# self._queued_command.notify_finish(final_message)
completed = False

# print(pipe.stdout.readline().decode('utf-8'))
# print(pipe.stderr.readline().decode('utf-8'))
# print(pipe.stdout.readline().decode('utf-8'))
# print(pipe.stderr.readline().decode('utf-8'))

with disks_lock:
update_disks_if_needed(self)
Expand Down
2 changes: 1 addition & 1 deletion constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
QUEUE_TABLE_PROGRESS = 4

QUEUE_LABELS = {
"queued_badblocks": "Erase",
"queued_erase": "Erase",
"queued_smartctl": "Smart Check",
"smartctl": "Smart Check",
"queued_cannolo": "Load System",
Expand Down
6 changes: 3 additions & 3 deletions pinolo.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ def refresh(self):

def standard_procedure(self):
"""This function send to the server a sequence of commands:
- queued_badblocks
- queued_erase
- queued_smartctl
- queued_cannolo (if the cannolo flag on the dialog is checked)
- queued_sleep
Expand All @@ -401,7 +401,7 @@ def standard_procedure(self):
self.load_system(standard_procedure=True)

def erase(self, standard_procedure=False):
"""This function send to the server a queued_badblocks command.
"""This function send to the server a queued_erase command.
If "std" is True it will skip the confirm dialog."""

rows = self.drivesTableView.selectionModel().selectedRows()
Expand All @@ -415,7 +415,7 @@ def erase(self, standard_procedure=False):
if critical_dialog(message, dialog_type="yes_no") != QMessageBox.Yes:
return
for drive in drives:
self.send_command(f"queued_badblocks {drive.name}")
self.send_command(f"queued_erase {drive.name}")

def smart_check(self):
"""This function send to the server a queued_smartctl command.
Expand Down
Loading