diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1baa848 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,93 @@ +name: Build and test + +on: + push: + branches: + - develop + - main + - master + pull_request: + +jobs: + ubuntu: + name: Ubuntu build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + autoconf \ + automake \ + build-essential \ + lsb-release \ + libncurses-dev \ + ntfs-3g \ + ntfs-3g-dev + + - name: Bootstrap + run: ./autogen.sh + + - name: Configure + run: ./configure + + - name: Build + run: make + + - name: Test + run: make check + + - name: Test fragmented NTFS image + run: sudo ./TestSuite/Create\ Fragmented\ Volumes/ci_fragmented_volume_test.sh ./udefrag + + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: udefrag-linux + path: udefrag + if-no-files-found: error + + cachyos: + name: CachyOS build + runs-on: ubuntu-latest + container: + image: cachyos/cachyos:latest + + steps: + - name: Install dependencies + run: | + pacman -Syu --noconfirm + pacman -S --noconfirm --needed \ + autoconf \ + automake \ + base-devel \ + grep \ + ncurses \ + ntfs-3g + + - name: Checkout + uses: actions/checkout@v4 + + - name: Bootstrap + run: ./autogen.sh + + - name: Configure + run: ./configure + + - name: Build + run: make + + - name: Test + run: make check + + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: udefrag-cachyos + path: udefrag + if-no-files-found: error + diff --git a/.gitignore b/.gitignore index 00f67fb..4e05708 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,61 @@ -# Created by https://www.gitignore.io/api/c,c++,linux,macos,windows -# Edit at https://www.gitignore.io/?templates=c,c++,linux,macos,windows +# Created by https://www.toptal.com/developers/gitignore/api/c,c++,linux,macos,windows,git,autotools,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=c,c++,linux,macos,windows,git,autotools,visualstudiocode + +### Autotools ### +# http://www.gnu.org/software/automake + +Makefile.in +/ar-lib +/mdate-sh +/py-compile +/test-driver +/ylwrap +.deps/ +.dirstamp + +# http://www.gnu.org/software/autoconf + +autom4te.cache +/autoscan.log +/autoscan-*.log +/aclocal.m4 +/compile +/config.cache +/config.guess +/config.h.in +/config.log +/config.status +/config.sub +/configure +/configure.scan +/depcomp +/install-sh +/missing +/stamp-h1 + +# https://www.gnu.org/software/libtool/ + +/ltmain.sh + +# http://www.gnu.org/software/texinfo + +/texinfo.tex + +# http://www.gnu.org/software/m4/ + +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 + +# Generated Makefile +# (meta build system like autotools, +# can automatically generate from config.status script +# (which is called by configure script)) +Makefile + +### Autotools Patch ### ### C ### # Prerequisites @@ -74,6 +130,21 @@ dkms.conf # Executables +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + ### Linux ### *~ @@ -98,6 +169,7 @@ dkms.conf # Icon must end with two \r Icon + # Thumbnails ._* @@ -117,6 +189,29 @@ Network Trash Folder Temporary Items .apdisk +### macOS Patch ### +# iCloud generated files +*.icloud + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + ### Windows ### # Windows thumbnail cache files Thumbs.db @@ -143,8 +238,24 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk -# End of https://www.gitignore.io/api/c,c++,linux,macos,windows +# End of https://www.toptal.com/developers/gitignore/api/c,c++,linux,macos,windows,git,autotools,visualstudiocode .git udefrag + +# Autotools generated files +/autotools-config.h +/autotools-config.h.in +/ultradefrag4linux-*/ +/ultradefrag4linux-*.tar.* +/.deps/ +**/.deps/ + +# Legacy Windows/MinGW build helpers and generated files +/ci/bin/ +Makefile.mingw +Makefile_x64.mingw +/src/console/getopt.c +/src/console/getopt.h +/src/console/getopt1.c diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..b54b903 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,80 @@ +AUTOMAKE_OPTIONS = foreign subdir-objects + +AM_CPPFLAGS = \ + -DLXGC=1 \ + $(NTFS3G_CFLAGS) \ + -I$(top_srcdir)/src/include \ + -I$(top_srcdir)/src/console \ + -I$(top_srcdir)/src/dll/udefrag \ + -I$(top_srcdir)/src/dll/zenwinx \ + -I$(top_srcdir)/src/wincalls + +bin_PROGRAMS = udefrag + +noinst_HEADERS = \ + src/console/udefrag.h \ + src/include/compiler.h \ + src/include/extrawin.h \ + src/include/linux.h \ + src/include/ultradfgver.h \ + src/include/version.h \ + src/share/getopt.h \ + src/dll/udefrag/udefrag.h \ + src/dll/udefrag/udefrag-internals.h \ + src/dll/zenwinx/myendians.h \ + src/dll/zenwinx/mytypes.h \ + src/dll/zenwinx/ntndk.h \ + src/dll/zenwinx/ntfs.h \ + src/dll/zenwinx/partition.h \ + src/dll/zenwinx/prb.h \ + src/dll/zenwinx/zenwinx.h \ + src/dll/zenwinx/zenwinxver.h \ + src/wincalls/ntfs-3g.h + +EXTRA_DIST = autogen.sh + +udefrag_SOURCES = \ + src/console/defrag.c \ + src/console/map.c \ + src/console/options.c \ + src/dll/udefrag/analyze.c \ + src/dll/udefrag/defrag.c \ + src/dll/udefrag/map.c \ + src/dll/udefrag/move.c \ + src/dll/udefrag/optimize.c \ + src/dll/udefrag/options.c \ + src/dll/udefrag/reports.c \ + src/dll/udefrag/search.c \ + src/dll/udefrag/tasks.c \ + src/dll/udefrag/udefrag.c \ + src/dll/udefrag/volume.c \ + src/dll/zenwinx/dbg.c \ + src/dll/zenwinx/env.c \ + src/dll/zenwinx/event.c \ + src/dll/zenwinx/file.c \ + src/dll/zenwinx/ftw.c \ + src/dll/zenwinx/ftw_ntfs.c \ + src/dll/zenwinx/ldr.c \ + src/dll/zenwinx/list.c \ + src/dll/zenwinx/lock.c \ + src/dll/zenwinx/mem.c \ + src/dll/zenwinx/misc.c \ + src/dll/zenwinx/path.c \ + src/dll/zenwinx/prb.c \ + src/dll/zenwinx/string.c \ + src/dll/zenwinx/thread.c \ + src/dll/zenwinx/time.c \ + src/dll/zenwinx/volume.c \ + src/dll/zenwinx/zenwinx.c \ + src/wincalls/wincalls.c \ + src/wincalls/ntfs-3g.c \ + src/wincalls/curses.c + +udefrag_LDADD = \ + $(NTFS3G_LIBS) \ + $(PTHREAD_LIBS) \ + $(CURSES_LIBS) \ + -lm + +check-local: udefrag + ./udefrag --help | $(GREP) -q "Usage: udefrag" diff --git a/README.md b/README.md index 9db62de..6ffb9ad 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,10 @@ The results at the time were clear, no changes just a GUI on top. ## Compiling ```sh -cd src -CFLAGS='-Wno-implict-function-declaration' make +./autogen.sh +./configure +make +make check ``` ## Support diff --git a/TestSuite/Create Fragmented Volumes/ci_fragmented_volume_test.ps1 b/TestSuite/Create Fragmented Volumes/ci_fragmented_volume_test.ps1 new file mode 100644 index 0000000..014fbdf --- /dev/null +++ b/TestSuite/Create Fragmented Volumes/ci_fragmented_volume_test.ps1 @@ -0,0 +1,301 @@ +param( + [string]$BinaryPath = "src\bin\amd64\udefrag.exe", + [string]$DriveLetter = "T", + [int]$VolumeSizeMB = 1024, + [int]$FillerFileCount = 300, + [int]$FillerFileSizeMB = 3, + [int]$FragmentedFileCount = 40, + [int]$FragmentedFileSizeMB = 5, + [string[]]$FileSystems = @( + "FAT", + "FAT32", + "exFAT", + "NTFS", + "NTFS_COMPRESSED", + "NTFS_MIXED", + "UDF_102", + "UDF_150", + "UDF_200", + "UDF_201", + "UDF_250", + "UDF_250_DUP" + ), + [switch]$FullFormat +) + +$ErrorActionPreference = "Stop" + +function Write-Step { + param([string]$Message) + Write-Host "==> $Message" +} + +function Invoke-Checked { + param( + [string]$FilePath, + [string[]]$ArgumentList + ) + + Write-Host "+ $FilePath $($ArgumentList -join ' ')" + & $FilePath @ArgumentList + if ($LASTEXITCODE -ne 0) { + throw "$FilePath exited with code $LASTEXITCODE" + } +} + +function Invoke-DiskPart { + param([string[]]$Commands) + + $scriptPath = Join-Path $env:TEMP ("udefrag-diskpart-{0}.txt" -f ([guid]::NewGuid())) + try { + $Commands | Set-Content -Path $scriptPath -Encoding ASCII + Invoke-Checked -FilePath "diskpart.exe" -ArgumentList @("/s", $scriptPath) + } finally { + Remove-Item -Path $scriptPath -Force -ErrorAction SilentlyContinue + } +} + +function Test-Administrator { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal]::new($identity) + return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +function Resolve-TestDriveLetter { + param([string]$PreferredDriveLetter) + + $preferred = $PreferredDriveLetter.TrimEnd(":") + if (-not (Test-Path "$preferred`:\")) { + return $preferred + } + + foreach ($candidate in @("T", "U", "V", "W", "X", "Y", "Z")) { + if (-not (Test-Path "$candidate`:\")) { + return $candidate + } + } + + throw "No free drive letter is available for the fragmented volume test." +} + +function Get-TestFileSystem { + param([string]$Name) + + switch ($Name.ToUpperInvariant()) { + "FAT" { + return [pscustomobject]@{ Name = "FAT"; FormatFs = "FAT"; UdfRevision = $null; UdfDuplicate = $false; Compression = "none" } + } + "FAT32" { + return [pscustomobject]@{ Name = "FAT32"; FormatFs = "FAT32"; UdfRevision = $null; UdfDuplicate = $false; Compression = "none" } + } + "EXFAT" { + return [pscustomobject]@{ Name = "exFAT"; FormatFs = "exFAT"; UdfRevision = $null; UdfDuplicate = $false; Compression = "none" } + } + "NTFS" { + return [pscustomobject]@{ Name = "NTFS"; FormatFs = "NTFS"; UdfRevision = $null; UdfDuplicate = $false; Compression = "none" } + } + "NTFS_COMPRESSED" { + return [pscustomobject]@{ Name = "NTFS compressed"; FormatFs = "NTFS"; UdfRevision = $null; UdfDuplicate = $false; Compression = "all" } + } + "NTFS_MIXED" { + return [pscustomobject]@{ Name = "NTFS mixed"; FormatFs = "NTFS"; UdfRevision = $null; UdfDuplicate = $false; Compression = "mixed" } + } + "UDF_102" { + return [pscustomobject]@{ Name = "UDF v1.02"; FormatFs = "UDF"; UdfRevision = "1.02"; UdfDuplicate = $false; Compression = "none" } + } + "UDF_150" { + return [pscustomobject]@{ Name = "UDF v1.50"; FormatFs = "UDF"; UdfRevision = "1.50"; UdfDuplicate = $false; Compression = "none" } + } + "UDF_200" { + return [pscustomobject]@{ Name = "UDF v2.00"; FormatFs = "UDF"; UdfRevision = "2.00"; UdfDuplicate = $false; Compression = "none" } + } + "UDF_201" { + return [pscustomobject]@{ Name = "UDF v2.01"; FormatFs = "UDF"; UdfRevision = "2.01"; UdfDuplicate = $false; Compression = "none" } + } + "UDF_250" { + return [pscustomobject]@{ Name = "UDF v2.50"; FormatFs = "UDF"; UdfRevision = "2.50"; UdfDuplicate = $false; Compression = "none" } + } + "UDF_250_DUP" { + return [pscustomobject]@{ Name = "UDF v2.50 duplicated metadata"; FormatFs = "UDF"; UdfRevision = "2.50"; UdfDuplicate = $true; Compression = "none" } + } + default { + throw "Unsupported filesystem test case: $Name" + } + } +} + +function Format-TestVolume { + param( + [pscustomobject]$FileSystem, + [string]$Drive + ) + + $formatMode = if ($FullFormat) { "" } else { "quick" } + if ($FileSystem.FormatFs -eq "UDF") { + $args = @($Drive, "/FS:UDF", "/V:UDFRAGCI", "/Y") + if (-not $FullFormat) { + $args += "/Q" + } + if ($FileSystem.UdfRevision) { + $args += "/R:$($FileSystem.UdfRevision)" + } + if ($FileSystem.UdfDuplicate) { + $args += "/D" + } + Invoke-Checked -FilePath "format.com" -ArgumentList $args + return + } + + Invoke-DiskPart @( + "select volume $($Drive.TrimEnd(':'))", + "format fs=$($FileSystem.FormatFs) label=UDFRAGCI $formatMode" + ) +} + +function Get-ExtentCount { + param([string]$Path) + + $output = & fsutil.exe file queryextents $Path 2>$null + if ($LASTEXITCODE -ne 0) { + return 0 + } + + return (($output | Select-String -Pattern "VCN:").Count) +} + +function New-SizedFile { + param( + [string]$Path, + [int]$SizeMB + ) + + Invoke-Checked -FilePath "fsutil.exe" -ArgumentList @( + "file", "createnew", $Path, (($SizeMB * 1MB).ToString()) + ) +} + +function New-FragmentedDataset { + param( + [string]$Root, + [string]$CompressionMode + ) + + $fillerRoot = Join-Path $Root "filler" + $fragmentRoot = Join-Path $Root "fragmented" + New-Item -ItemType Directory -Force -Path $fillerRoot, $fragmentRoot | Out-Null + + if ($CompressionMode -eq "all") { + Invoke-Checked -FilePath "compact.exe" -ArgumentList @("/C", $fillerRoot, $fragmentRoot) + } + + Write-Step "Creating allocation pressure files" + for ($i = 0; $i -lt $FillerFileCount; $i++) { + $file = Join-Path $fillerRoot ("filler-{0:D4}.bin" -f $i) + New-SizedFile -Path $file -SizeMB $FillerFileSizeMB + } + + Write-Step "Removing alternating files to create free-space holes" + for ($i = 0; $i -lt $FillerFileCount; $i += 2) { + Remove-Item -Force (Join-Path $fillerRoot ("filler-{0:D4}.bin" -f $i)) + } + + Write-Step "Creating test files expected to span multiple holes" + for ($i = 0; $i -lt $FragmentedFileCount; $i++) { + $file = Join-Path $fragmentRoot ("fragmented-{0:D4}.bin" -f $i) + New-SizedFile -Path $file -SizeMB $FragmentedFileSizeMB + if ($CompressionMode -eq "mixed" -and ($i % 4) -eq 0) { + Invoke-Checked -FilePath "compact.exe" -ArgumentList @("/C", $file) + } + } + + if ($CompressionMode -eq "all") { + Invoke-Checked -FilePath "compact.exe" -ArgumentList @("/C", "/S:$Root") + } + + $fragmentedFiles = Get-ChildItem -Path $fragmentRoot -Filter "*.bin" + $extentCounts = foreach ($file in $fragmentedFiles) { + Get-ExtentCount -Path $file.FullName + } + + $fragmentedCount = ($extentCounts | Where-Object { $_ -gt 1 }).Count + $maxExtents = ($extentCounts | Measure-Object -Maximum).Maximum + if ($null -eq $maxExtents) { + $maxExtents = 0 + } + + Write-Host "Fragmented files detected: $fragmentedCount/$($fragmentedFiles.Count); max extents: $maxExtents" + if ($fragmentedCount -eq 0) { + Write-Warning "No fragmented files were detected. Continuing because allocation behavior can vary on virtual disks." + } +} + +function Invoke-FragmentedVolumeCase { + param( + [pscustomobject]$FileSystem, + [string]$Binary, + [string]$ResolvedDriveLetter + ) + + $drive = "$ResolvedDriveLetter`:" + $safeName = ($FileSystem.Name -replace "[^A-Za-z0-9]+", "-").Trim("-").ToLowerInvariant() + $vhdPath = Join-Path $env:TEMP "udefrag-fragmented-volume-$safeName.vhd" + $testRoot = "$drive\udefrag-ci" + + try { + Write-Step "Creating temporary ${VolumeSizeMB}MB VHD for $($FileSystem.Name) at $vhdPath" + Remove-Item -Path $vhdPath -Force -ErrorAction SilentlyContinue + Invoke-DiskPart @( + "create vdisk file=`"$vhdPath`" maximum=$VolumeSizeMB type=fixed", + "select vdisk file=`"$vhdPath`"", + "attach vdisk", + "create partition primary", + "assign letter=$ResolvedDriveLetter" + ) + + Write-Step "Formatting test volume as $($FileSystem.Name)" + Format-TestVolume -FileSystem $FileSystem -Drive $drive + + Write-Step "Running pre-test CHKDSK for $($FileSystem.Name)" + Invoke-Checked -FilePath "chkdsk.exe" -ArgumentList @($drive) + + New-FragmentedDataset -Root $testRoot -CompressionMode $FileSystem.Compression + + Write-Step "Analyzing fragmented $($FileSystem.Name) test volume" + Invoke-Checked -FilePath $Binary -ArgumentList @("--analyze", $drive) + + Write-Step "Running dry-run defragmentation against $($FileSystem.Name) test volume" + $env:UD_DRY_RUN = "1" + Invoke-Checked -FilePath $Binary -ArgumentList @("--defragment", $drive) + + Write-Step "Running post-test CHKDSK for $($FileSystem.Name)" + Invoke-Checked -FilePath "chkdsk.exe" -ArgumentList @($drive) + } finally { + Remove-Item Env:\UD_DRY_RUN -ErrorAction SilentlyContinue + + Write-Step "Detaching and deleting temporary VHD for $($FileSystem.Name)" + try { + Invoke-DiskPart @( + "select vdisk file=`"$vhdPath`"", + "detach vdisk" + ) + } catch { + Write-Warning $_ + } + Remove-Item -Path $vhdPath -Force -ErrorAction SilentlyContinue + } +} + +if (-not (Test-Administrator)) { + throw "The fragmented volume test requires an elevated Windows runner." +} + +$binary = Resolve-Path $BinaryPath +$resolvedDriveLetter = Resolve-TestDriveLetter -PreferredDriveLetter $DriveLetter +$fileSystemCases = foreach ($fs in $FileSystems) { + Get-TestFileSystem -Name $fs +} + +foreach ($fs in $fileSystemCases) { + Write-Step "Starting filesystem case: $($fs.Name)" + Invoke-FragmentedVolumeCase -FileSystem $fs -Binary $binary.Path -ResolvedDriveLetter $resolvedDriveLetter +} diff --git a/TestSuite/Create Fragmented Volumes/ci_fragmented_volume_test.sh b/TestSuite/Create Fragmented Volumes/ci_fragmented_volume_test.sh new file mode 100755 index 0000000..0cf35c8 --- /dev/null +++ b/TestSuite/Create Fragmented Volumes/ci_fragmented_volume_test.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +set -euo pipefail + +binary="${1:-./udefrag}" +volume_size_mb="${VOLUME_SIZE_MB:-1024}" +filler_file_count="${FILLER_FILE_COUNT:-300}" +filler_file_size_mb="${FILLER_FILE_SIZE_MB:-3}" +fragmented_file_count="${FRAGMENTED_FILE_COUNT:-40}" +fragmented_file_size_mb="${FRAGMENTED_FILE_SIZE_MB:-5}" +supported_filesystems="${SUPPORTED_FILESYSTEMS:-ntfs}" + +if [[ "$(id -u)" -ne 0 ]]; then + echo "This test creates and mounts a temporary NTFS image; run it as root." >&2 + exit 1 +fi + +if [[ ! -x "$binary" ]]; then + echo "Binary not found or not executable: $binary" >&2 + exit 1 +fi + +for tool in mkntfs ntfs-3g ntfsfix umount dd truncate; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "Required tool is missing: $tool" >&2 + exit 1 + fi +done + +workdir="$(mktemp -d "${TMPDIR:-/tmp}/udefrag-fragmented-volume.XXXXXX")" +mountpoint="$workdir/mnt" +mounted=0 + +cleanup() { + if [[ "$mounted" -eq 1 ]]; then + sync || true + umount "$mountpoint" || true + fi + rm -rf "$workdir" +} +trap cleanup EXIT + +step() { + printf '==> %s\n' "$*" +} + +run() { + printf '+' + printf ' %q' "$@" + printf '\n' + "$@" +} + +create_file() { + local path="$1" + local size_mb="$2" + + dd if=/dev/zero of="$path" bs=1M count="$size_mb" status=none conv=fsync +} + +run_ntfs_case() { + local image="$workdir/udefrag-fragmented-volume-ntfs.img" + local filler_root + local fragment_root + + step "Creating temporary ${volume_size_mb}MB NTFS image at $image" + mkdir -p "$mountpoint" + run truncate -s "${volume_size_mb}M" "$image" + run mkntfs -F -Q -L UDFRAGCI "$image" + + step "Checking fresh NTFS image" + run ntfsfix -n "$image" + + step "Mounting NTFS image" + run ntfs-3g "$image" "$mountpoint" + mounted=1 + + filler_root="$mountpoint/udefrag-ci/filler" + fragment_root="$mountpoint/udefrag-ci/fragmented" + mkdir -p "$filler_root" "$fragment_root" + + step "Creating allocation pressure files" + for ((i = 0; i < filler_file_count; i++)); do + create_file "$filler_root/filler-$(printf '%04d' "$i").bin" "$filler_file_size_mb" + done + + step "Removing alternating files to create free-space holes" + for ((i = 0; i < filler_file_count; i += 2)); do + rm -f "$filler_root/filler-$(printf '%04d' "$i").bin" + done + + step "Creating test files expected to span multiple holes" + for ((i = 0; i < fragmented_file_count; i++)); do + create_file "$fragment_root/fragmented-$(printf '%04d' "$i").bin" "$fragmented_file_size_mb" + done + + sync + step "Unmounting NTFS image before defragmenter run" + run umount "$mountpoint" + mounted=0 + + step "Checking populated NTFS image" + run ntfsfix -n "$image" + + step "Analyzing fragmented NTFS image" + run "$binary" --analyze "$image" + + step "Running dry-run defragmentation against fragmented NTFS image" + run env UD_DRY_RUN=1 "$binary" --defragment "$image" + + step "Checking NTFS image after dry-run" + run ntfsfix -n "$image" +} + +for fs in $supported_filesystems; do + case "${fs,,}" in + ntfs) + step "Starting filesystem case: NTFS" + run_ntfs_case + ;; + *) + echo "Unsupported Linux filesystem test case: $fs" >&2 + echo "The Linux build currently supports NTFS device images only." >&2 + exit 1 + ;; + esac +done diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..1dd5220 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +autoreconf -fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..89b95e8 --- /dev/null +++ b/configure.ac @@ -0,0 +1,57 @@ +AC_INIT([UltraDefrag4Linux], [5.0.0AB.8], [https://github.com/749/UltraDefrag4Linux/issues]) +AM_INIT_AUTOMAKE([foreign subdir-objects no-define]) +AC_CONFIG_SRCDIR([src/console/defrag.c]) + +AC_PROG_CC +AC_PROG_GREP +AC_PROG_RANLIB +AM_PROG_AR + +AC_ARG_WITH( + [ntfs3g-includedir], + [AS_HELP_STRING([--with-ntfs3g-includedir=DIR], [directory containing ntfs-3g headers])], + [ntfs3g_includedir="$withval"], + [ntfs3g_includedir=""]) + +AS_IF([test -z "$ntfs3g_includedir" && test -d /usr/include/ntfs-3g], + [ntfs3g_includedir="/usr/include/ntfs-3g"]) + +AS_IF([test -n "$ntfs3g_includedir"], + [NTFS3G_CFLAGS="-I$ntfs3g_includedir"], + [NTFS3G_CFLAGS=""]) + +AS_IF([test -z "$ntfs3g_includedir" || test ! -f "$ntfs3g_includedir/volume.h"], + [AC_MSG_ERROR([required ntfs-3g headers were not found; install ntfs-3g-dev or use --with-ntfs3g-includedir])]) + +AC_ARG_WITH( + [ntfs3g-libdir], + [AS_HELP_STRING([--with-ntfs3g-libdir=DIR], [directory containing libntfs-3g])], + [NTFS3G_LDFLAGS="-L$withval"], + [AS_IF([test -d /usr/lib/x86_64-linux-gnu], + [NTFS3G_LDFLAGS="-L/usr/lib/x86_64-linux-gnu"], + [NTFS3G_LDFLAGS=""])]) + +save_LDFLAGS=$LDFLAGS +LDFLAGS="$LDFLAGS $NTFS3G_LDFLAGS" +AC_CHECK_LIB([ntfs-3g], [ntfs_mount], + [NTFS3G_LIBS="$NTFS3G_LDFLAGS -lntfs-3g"], + [AC_MSG_ERROR([required ntfs-3g library was not found])]) +LDFLAGS=$save_LDFLAGS + +AC_CHECK_LIB([pthread], [pthread_create], + [PTHREAD_LIBS="-lpthread"], + [AC_MSG_ERROR([required pthread library was not found])]) + +AC_CHECK_LIB([ncurses], [initscr], + [CURSES_LIBS="-lncurses"], + [AC_CHECK_LIB([curses], [initscr], + [CURSES_LIBS="-lcurses"], + [AC_MSG_ERROR([required curses/ncurses library was not found])])]) + +AC_SUBST([NTFS3G_CFLAGS]) +AC_SUBST([NTFS3G_LIBS]) +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([CURSES_LIBS]) + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/src/console/defrag.c b/src/console/defrag.c index 963613b..103737d 100644 --- a/src/console/defrag.c +++ b/src/console/defrag.c @@ -30,6 +30,7 @@ #ifdef LINUX #include #include +#include #endif #include "extrawin.h" @@ -468,7 +469,7 @@ void update_progress(udefrag_progress_info *pi, void *p) char *op_name = ""; char *results; #if defined(CURSES) | defined(LINUX) - char buf[120]; + char buf[512]; #ifdef LINUX char *volume; #else @@ -521,17 +522,17 @@ void update_progress(udefrag_progress_info *pi, void *p) #endif if(pi->current_operation == VOLUME_OPTIMIZATION && !stop_flag && pi->completion_status == 0){ if(pi->pass_number > 1) - sprintf(buf,"%s: %s%6.2lf%% complete, pass %lu, moves total = %" LL64 "u", + snprintf(buf,sizeof(buf),"%s: %s%6.2lf%% complete, pass %lu, moves total = %" LL64 "u", volume,op_name,pi->percentage,pi->pass_number,(ULONGLONG)pi->total_moves); else - sprintf(buf,"%s: %s%6.2lf%% complete, moves total = %" LL64 "u", + snprintf(buf,sizeof(buf),"%s: %s%6.2lf%% complete, moves total = %" LL64 "u", volume,op_name,pi->percentage,(ULONGLONG)pi->total_moves); } else { if(pi->pass_number > 1) - sprintf(buf,"%s: %s%6.2lf%% complete, pass %lu, fragmented/total = %lu/%lu", + snprintf(buf,sizeof(buf),"%s: %s%6.2lf%% complete, pass %lu, fragmented/total = %lu/%lu", volume,op_name,pi->percentage,pi->pass_number,pi->fragmented,pi->files); else - sprintf(buf,"%s: %s%6.2lf%% complete, fragmented/total = %lu/%lu", + snprintf(buf,sizeof(buf),"%s: %s%6.2lf%% complete, fragmented/total = %lu/%lu", volume,op_name,pi->percentage,pi->fragmented,pi->files); } if (m_flag) @@ -542,10 +543,10 @@ void update_progress(udefrag_progress_info *pi, void *p) if (pi->completion_status != 0 && !stop_flag) { /* set progress indicator to 100% state */ if(pi->pass_number > 1) - sprintf(buf,"%s: %s100.00%% complete, %lu passes needed, fragmented/total = %lu/%lu", + snprintf(buf,sizeof(buf),"%s: %s100.00%% complete, %lu passes needed, fragmented/total = %lu/%lu", volume,op_name,pi->pass_number,pi->fragmented,pi->files); else - sprintf(buf,"%s: %s100.00%% complete, fragmented/total = %lu/%lu", + snprintf(buf,sizeof(buf),"%s: %s100.00%% complete, fragmented/total = %lu/%lu", volume,op_name,pi->fragmented,pi->files); if (m_flag) set_message(ROW_PROGRESS,0, @@ -730,7 +731,7 @@ static int process_volumes(void) #if STSC f = open(path->path,O_RDONLY); #else - f = open64(path->path,O_RDONLY); + f = open(path->path,O_RDONLY); #endif if (f > 0) { n = read(f,buf,512); diff --git a/src/dll/udefrag/udefrag-internals.h b/src/dll/udefrag/udefrag-internals.h index dd4161b..0034b5e 100644 --- a/src/dll/udefrag/udefrag-internals.h +++ b/src/dll/udefrag/udefrag-internals.h @@ -267,6 +267,9 @@ void stop_timing(char *operation_name,ULONGLONG start_time,udefrag_job_parameter int check_region(udefrag_job_parameters *jp,ULONGLONG lcn,ULONGLONG length); winx_blockmap *get_first_block_of_cluster_chain(winx_file_info *f,ULONGLONG vcn); +#ifdef LINUXMODE +void deliver_progress_info(udefrag_job_parameters *jp,int completion_status); +#endif NTSTATUS udefrag_fopen(winx_file_info *f,HANDLE *phFile); int is_file_locked(winx_file_info *f,udefrag_job_parameters *jp); int is_mft(winx_file_info *f,udefrag_job_parameters *jp); diff --git a/src/include/linux.h b/src/include/linux.h index 1648ce3..174d4d1 100644 --- a/src/include/linux.h +++ b/src/include/linux.h @@ -258,11 +258,15 @@ DWORD WINAPI GetEnvironmentVariableW(const utf_t*, utf_t*, DWORD); BOOLEAN WINAPI SetEnvironmentVariableA(const char*, const char*); BOOLEAN WINAPI SetEnvironmentVariableW(const utf_t*, const utf_t*); DWORD WINAPI GetLastError(void); +DWORD WINAPI FormatMessage(DWORD, LPCVOID, DWORD, DWORD, LPSTR, DWORD, va_list*); +DWORD WINAPI FormatMessageA(DWORD, LPCVOID, DWORD, DWORD, LPSTR, DWORD, va_list*); +LPVOID LocalFree(LPVOID); VOID WINAPI RtlZeroMemory(VOID*, SIZE_T); /* not WINAPI according to msdn */ BOOLEAN WINAPI SetConsoleCursorPosition(HANDLE, COORD); BOOLEAN WINAPI SetConsoleTextAttribute(HANDLE, WORD); BOOLEAN WINAPI GetConsoleScreenBufferInfo(HANDLE, CONSOLE_SCREEN_BUFFER_INFO*); BOOLEAN WINAPI SetConsoleWindowInfo(HANDLE, BOOLEAN, const SMALL_RECT*); +BOOLEAN WINAPI SetConsoleCtrlHandler(PHANDLER_ROUTINE, BOOLEAN); DWORD MAKELANGID(DWORD, DWORD); /* * Extended C library for processing utf16le strings @@ -287,6 +291,8 @@ char *_strupr(char*); char *_strlwr(char*); int _vsnprintf(char*, size_t sz, const char*, va_list); +int _snprintf(char*, int, const char*, ...); +utf_t *_wcsupr(utf_t*); int _snwprintf(utf_t*, size_t, const utf_t*, ...); #if WNSC | STSC | SPGC int safe_fprintf(struct _iobuf*, const char*, ...); @@ -296,6 +302,12 @@ int safe_fprintf(struct _IO_FILE*, const char*, ...); void safe_dump(struct _IO_FILE*, const char*, const char*, int); #endif const char *calledfrom(void*); +void WgxDbgPrintLastError(const char*, ...); +void display_error(char*); +int ntfs_mounted_device(const char*); +int _getch(void); +int winx_bytes_to_hr(ULONGLONG, int, char*, int); +void winx_unload_library(void); /* * Miscellaneous @@ -308,6 +320,7 @@ const char *calledfrom(void*); void stop_there(int, const char*, int); #define get_out(n) stop_there(n,__FILE__,__LINE__) void initwincalls(void); +void endwincalls(void); /* * Memory allocation through libntfs-3g diff --git a/src/wincalls/curses.c b/src/wincalls/curses.c index 18be20b..c528ddc 100644 --- a/src/wincalls/curses.c +++ b/src/wincalls/curses.c @@ -36,6 +36,7 @@ #define trace stdtrace /* symbol conflict with curses */ #include +#include #undef trace #ifndef UNTHREADED diff --git a/src/wincalls/ntfs-3g.c b/src/wincalls/ntfs-3g.c index c2031a3..b73e35b 100644 --- a/src/wincalls/ntfs-3g.c +++ b/src/wincalls/ntfs-3g.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include diff --git a/src/wincalls/ntfs-3g.h b/src/wincalls/ntfs-3g.h index ca69669..9a86f56 100644 --- a/src/wincalls/ntfs-3g.h +++ b/src/wincalls/ntfs-3g.h @@ -25,6 +25,7 @@ */ HANDLE ntfs_open(const utf_t*); +int ntfs_mounted_device(const char*); int ntfs_close(HANDLE); int ntfs_unlink(HANDLE, const char*); int ntfs_sync(HANDLE); diff --git a/src/wincalls/wincalls.c b/src/wincalls/wincalls.c index f92bbfb..cb77e67 100644 --- a/src/wincalls/wincalls.c +++ b/src/wincalls/wincalls.c @@ -39,6 +39,7 @@ #include //#include #include +#include #include #include #include @@ -50,6 +51,11 @@ #include "ntfs-3g.h" #include "extrawin.h" +#ifdef CURSES +int curs_set(int); +int endwin(void); +#endif + #if STSC #undef USETIMEOFDAY #else @@ -1197,7 +1203,11 @@ fprintf(stderr,"** Heap was not allocated\n"); NTSTATUS NTAPI NtAllocateVirtualMemory(HANDLE a, PVOID *b, SIZE_T c, SIZE_T *d, SIZE_T e, SIZE_T f) RUNDEF(NtAllocateVirtualMemory) -UNDEF(LocalFree) +LPVOID LocalFree(LPVOID p) +{ + free(p); + return (NULL); +} NTSTATUS NTAPI NtFreeVirtualMemory(HANDLE a, PVOID *b, SIZE_T *c, SIZE_T d) RUNDEF(NtFreeVirtualMemory) @@ -2136,7 +2146,14 @@ RUNDEF(SetConsoleWindowInfo) UNDEF(udefrag_fbsize) UNDEF(udefrag_toupper) -UNDEF(_wcsupr) +utf_t *_wcsupr(utf_t *s) +{ + utf_t *p; + + for (p = s; p && *p; p++) + *p = winx_toupper(*p); + return (s); +} utf16_t * WINAPI GetCommandLineW(void) { @@ -2298,14 +2315,14 @@ DWORD WINAPI FormatMessageA(DWORD flg, LPCVOID src, DWORD idmess, DWORD idlang, LPSTR buf, DWORD sz, va_list *args) //DWORD WINAPI FormatMessageA(long, char*, long, long, char*, long, va_list*) { - return (-1); /* return an error */ + return (0); /* return an error */ } DWORD WINAPI FormatMessage(DWORD flg, LPCVOID src, DWORD idmess, DWORD idlang, LPSTR buf, DWORD sz, va_list *args) //DWORD WINAPI FormatMessage(long, char*, long, long, char*, long, va_list*) { - return (-1); /* return an error */ + return (0); /* return an error */ } NTSTATUS NTAPI xNtCreateFile(PHANDLE ph, ACCESS_MASK acc, POBJECT_ATTRIBUTES p,