├── .gitattributes ├── .github └── workflows │ ├── mirror-main-branch.yaml │ └── pr-tests.yaml ├── .gitignore ├── Dockerfile ├── Dockerfile └── Readme.md ├── LICENSE ├── README.md ├── SubmittingPatches.rst ├── driver ├── common.h ├── debug.c ├── debug.h ├── driver.c ├── driver.h ├── options.c ├── options.h ├── scsi_driver_extensions.c ├── scsi_driver_extensions.h ├── scsi_function.c ├── scsi_function.h ├── scsi_operation.c ├── scsi_operation.h ├── scsi_trace.c ├── scsi_trace.h ├── srb_helper.h ├── userspace.c ├── userspace.h ├── util.c ├── util.h ├── wnbd.inf ├── wnbd.rc ├── wnbd_dispatch.c └── wnbd_dispatch.h ├── get_dependencies.ps1 ├── include ├── vendor.h ├── wnbd.h └── wnbd_ioctl.h ├── libwnbd ├── libwnbd.cpp ├── libwnbd.def ├── libwnbd.vcxproj ├── nbd_daemon.cpp ├── nbd_daemon.h ├── nbd_protocol.cpp ├── nbd_protocol.h ├── utils.cpp ├── utils.h ├── wnbd_ioctl.cpp ├── wnbd_log.c └── wnbd_log.h ├── packages.config ├── tests └── libwnbd_tests │ ├── libwnbd_tests.vcxproj │ ├── mock_wnbd_daemon.cc │ ├── mock_wnbd_daemon.h │ ├── options.cpp │ ├── options.h │ ├── pch.cpp │ ├── pch.h │ ├── request_log.cpp │ ├── request_log.h │ ├── test.cpp │ ├── test_adapter_actions.cpp │ ├── test_disk_actions.cpp │ ├── test_io.cpp │ ├── test_nbd.cpp │ ├── utils.cpp │ └── utils.h ├── vstudio ├── build_deps.vcxproj ├── driver.vcxproj ├── generate_version_h.ps1 ├── reinstall.ps1 ├── wnbd.sln └── wnbdevents.xml └── wnbd-client ├── client.cpp ├── client.h ├── cmd.cpp ├── cmd.h ├── code_page.manifest ├── main.cpp ├── usage.cpp ├── usage.h └── wnbd-client.vcxproj /.gitattributes: -------------------------------------------------------------------------------- 1 | *.vcxproj text eol=crlf 2 | *.sln text eol=crlf 3 | -------------------------------------------------------------------------------- /.github/workflows/mirror-main-branch.yaml: -------------------------------------------------------------------------------- 1 | name: Mirror main branch 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | master: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 # fetch the entire git history (the default is `fetch-depth: 1`) 20 | 21 | - name: Mirror to master branch 22 | run: | 23 | set -e 24 | if ! git show-ref --quiet master; then 25 | # create new branch if it doesn't already exist 26 | CHECKOUT_OPTS="-b" 27 | fi 28 | git checkout $CHECKOUT_OPTS master 29 | git rebase main 30 | git push origin master 31 | -------------------------------------------------------------------------------- /.github/workflows/pr-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request tests 2 | on: 3 | pull_request: 4 | branches: [ main ] 5 | jobs: 6 | static-analysis: 7 | runs-on: windows-2019 8 | timeout-minutes: 20 9 | steps: 10 | - uses: actions/checkout@v3 11 | with: 12 | fetch-depth: 0 13 | - uses: microsoft/setup-msbuild@v1.1 14 | with: 15 | msbuild-architecture: x64 16 | # stampinf.exe is unavailable with the 2019 image 17 | # unless we explicitly add the following to PATH 18 | - name: "Add toolkit bin dir to env PATH" 19 | run: > 20 | Add-Content $env:GITHUB_PATH 21 | "C:\Program Files (x86)\Windows Kits\10\bin\x64" 22 | - name: "Show git log" 23 | run: git log -n 10 --oneline 24 | # SDV is skipped for now because of a cryptic error: 25 | # c1 : fatal error C1250: Unable to load plug-in '\x22dcA»' 26 | # logs: vstudio/smvbuild.log 27 | # vstudio/sdv/smvcl.log 28 | - name: "Run static analysis tests" 29 | run: > 30 | msbuild vstudio\wnbd.sln 31 | /property:Configuration=Analyze 32 | /property:RunSDV=False 33 | build-and-test: 34 | timeout-minutes: 20 35 | strategy: 36 | matrix: 37 | configuration: [Debug, Release] 38 | runs-on: windows-2019 39 | steps: 40 | - uses: actions/checkout@v3 41 | with: 42 | fetch-depth: 0 43 | - uses: microsoft/setup-msbuild@v1.1 44 | with: 45 | msbuild-architecture: x64 46 | # stampinf.exe is unavailable with the 2019 image 47 | # unless we explicitly add the following to PATH 48 | - name: "Add toolkit bin dir to env PATH" 49 | run: > 50 | Add-Content $env:GITHUB_PATH 51 | "C:\Program Files (x86)\Windows Kits\10\bin\x64" 52 | - name: "Show git log" 53 | run: git log -n 10 --oneline 54 | - name: "Build WNBD" 55 | run: > 56 | msbuild vstudio\wnbd.sln 57 | /property:Configuration=${{ matrix.configuration }} 58 | - name: "Add WNBD build directory to env PATH" 59 | run: > 60 | Add-Content $env:GITHUB_PATH 61 | (Resolve-Path vstudio\x64\${{ matrix.configuration }}).Path 62 | - name: "Import WNBD certificate to root store" 63 | run: > 64 | Import-Certificate 65 | -FilePath vstudio\x64\${{ matrix.configuration }}\wnbd.cer 66 | -Cert Cert:\LocalMachine\Root 67 | - name: "Import WNBD certificate to trusted publisher store" 68 | run: > 69 | Import-Certificate 70 | -FilePath vstudio\x64\${{ matrix.configuration }}\wnbd.cer 71 | -Cert Cert:\LocalMachine\TrustedPublisher 72 | - name: "Import WNBD event definitions" 73 | run: wevtutil im vstudio\wnbdevents.xml 74 | - name: "Install the WNBD driver and create the virtual adapter" 75 | timeout-minutes: 1 76 | run: > 77 | wnbd-client install-driver --debug 78 | vstudio\x64\${{ matrix.configuration }}\driver\wnbd.inf 79 | - name: "Install qemu, used for NBD tests" 80 | run: choco install qemu --version 2023.4.24 -y 81 | - name: "Add qemu directory to env PATH" 82 | run: Add-Content $env:GITHUB_PATH 'C:\Program Files\qemu' 83 | - name: "Create qcow2 image, used for NBD" 84 | run: qemu-img create -f qcow2 test_5tb.qcow2 5T 85 | # cache.direct=on doesn't work with qcow2 images. Sparse files may not 86 | # be properly handled on Windows, so we'll avoid raw images for now. 87 | - name: "Start qemu-storage-daemon NBD server" 88 | run: > 89 | Start-Process qemu-storage-daemon 90 | -ArgumentList 91 | "--blockdev driver=file,node-name=file,filename=test_5tb.qcow2 92 | --blockdev driver=qcow2,node-name=qcow2,file=file 93 | --nbd-server addr.type=inet,addr.host=127.0.0.1,addr.port=10809 94 | --export type=nbd,id=export,node-name=qcow2,name=test_5tb,writable=on" 95 | - name: "Wait for the NBD server to start" 96 | run: sleep 2 97 | - name: "Run WNBD functional tests" 98 | run: > 99 | libwnbd_tests.exe 100 | --nbd-export-name test_5tb 101 | --nbd-hostname 127.0.0.1 102 | --nbd-port 10809 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #*# 2 | *.a 3 | *.d 4 | *.gcno 5 | *.gcda 6 | *.ko 7 | *.la 8 | *.lo 9 | *.loT 10 | *.mod.c 11 | *.o 12 | *.obj 13 | *.exe 14 | *.exp 15 | *.ilk 16 | *.lib 17 | *.pdb 18 | *.pyc 19 | *.retry 20 | *.so 21 | *.suo 22 | **/*.sym 23 | *~ 24 | *,cover 25 | .#* 26 | .*.cmd 27 | .*.swp 28 | *.vcxproj.user 29 | *.vcxproj.filters 30 | Module.symvers 31 | TAGS 32 | cscope.* 33 | tags 34 | **/.vs 35 | */x64/ 36 | /vstudio/SDV-default.xml 37 | /vstudio/runsdvui.cmd 38 | /vstudio/sdv-user.sdv 39 | /vstudio/smvbuild.log 40 | /vstudio/smvstats.txt 41 | /vstudio/wnbd.DVL.XML 42 | /vstudio/wnbd.inf 43 | /vstudio/x64/ 44 | /vstudio/sdv/ 45 | /vstudiox64/ 46 | /vstudio/deps 47 | /vstudio/packages 48 | /tests/libwnbd_tests/x64 49 | 50 | include/version.h 51 | -------------------------------------------------------------------------------- /Dockerfile/Dockerfile: -------------------------------------------------------------------------------- 1 | # escape=` 2 | 3 | ARG WIN_VER="ltsc2019" 4 | 5 | FROM mcr.microsoft.com/windows/servercore:$WIN_VER 6 | 7 | ADD https://aka.ms/vs/16/release/vs_buildtools.exe C:\TEMP\vs_buildtools.exe 8 | ADD https://chocolatey.org/install.ps1 C:\TEMP\choco-install.ps1 9 | ADD https://go.microsoft.com/fwlink/?linkid=2164149 C:\TEMP\wdksetup.exe 10 | 11 | # Let's be explicit about the shell that we're going to use. 12 | SHELL ["cmd", "/S", "/C"] 13 | 14 | # Install Build Tools. A 3010 error signals that requested operation is 15 | # successfull but changes will not be effective until the system is rebooted. 16 | RUN C:\TEMP\vs_buildtools.exe --quiet --wait --norestart --nocache ` 17 | --installPath C:\BuildTools ` 18 | --add Microsoft.VisualStudio.Workload.VCTools ` 19 | --add Microsoft.VisualStudio.Workload.MSBuildTools ` 20 | --add Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre ` 21 | --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` 22 | --add Microsoft.VisualStudio.Component.Windows10SDK.20348 ` 23 | --add Microsoft.VisualStudio.Component.VC.14.24.x86.x64 ` 24 | --add Microsoft.VisualStudio.Component.VC.14.24.x86.x64.Spectre ` 25 | || IF "%ERRORLEVEL%"=="3010" EXIT 0 26 | 27 | RUN powershell C:\TEMP\choco-install.ps1 28 | 29 | RUN choco install git -y 30 | 31 | # Install WDK excluding WDK.vsix. 32 | RUN C:\TEMP\wdksetup.exe /q 33 | 34 | # Install WDK.vsix in manual manner. 35 | RUN copy "C:\Program Files (x86)\Windows Kits\10\Vsix\VS2019\WDK.vsix" C:\TEMP\wdkvsix.zip 36 | RUN powershell Expand-Archive C:\TEMP\wdkvsix.zip -DestinationPath C:\TEMP\wdkvsix 37 | RUN robocopy.exe /e "C:\temp\wdkvsix\$MSBuild\Microsoft\VC\v160" "C:\BuildTools\MSBuild\Microsoft\VC\v160" || EXIT 0 38 | 39 | SHELL ["cmd"] 40 | 41 | CMD [ "cmd","/k","c:\\BuildTools\\VC\\Auxiliary\\Build\\vcvarsall.bat", "x86_x64", "10.0.18362.0" ] -------------------------------------------------------------------------------- /Dockerfile/Readme.md: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | This guide describes building WNBD using a Docker Windows container, 5 | leveraging the Docker file provided by WNBD. 6 | 7 | The resulting container provides all the build requirements (e.g. 8 | Visual Studio), without bloating the host. 9 | 10 | This simple example uses an interactive shell, feel free to automate 11 | the process, maybe copying the resulting binaries to a different 12 | location. 13 | 14 | Building the image 15 | ------------------ 16 | 17 | ```PowerShell 18 | docker build . -t wnbd_build 19 | ``` 20 | 21 | Run container using the resulting image 22 | --------------------------------------- 23 | 24 | The following command will enter an interactive container shell. 25 | This shell provides the prerequisites for building WNBD. 26 | 27 | ```PowerShell 28 | docker run -it wnbd_build 29 | ``` 30 | 31 | Clone and build WNBD via VS 2019 command prompt 32 | ----------------------------------------------- 33 | 34 | Run the following commands in the container interactive shell. 35 | 36 | ```PowerShell 37 | git clone https://github.com/cloudbase/wnbd 38 | msbuild wnbd\vstudio\wnbd.sln 39 | copy wnbd\vstudio\x64\Debug\driver\* . 40 | copy wnbd\vstudio\x64\Debug\wnbd-client.exe . 41 | copy wnbd\vstudio\x64\Debug\libwnbd.dll . 42 | ``` 43 | -------------------------------------------------------------------------------- /SubmittingPatches.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Submitting Patches to WNBD 3 | ========================== 4 | 5 | If you have a patch that fixes an issue, feel free to open a GitHub pull request 6 | ("PR") targeting the "main" branch, but do read this document first, as it 7 | contains important information for ensuring that your PR passes code review 8 | smoothly. 9 | 10 | .. contents:: 11 | :depth: 3 12 | 13 | Sign your work 14 | -------------- 15 | 16 | The sign-off is a simple line at the end of the explanation for the 17 | commit, which certifies that you wrote it or otherwise have the right to 18 | pass it on as a open-source patch. The rules are pretty simple: if you 19 | can certify the below: 20 | 21 | Developer's Certificate of Origin 1.1 22 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 23 | 24 | By making a contribution to this project, I certify that: 25 | 26 | (a) The contribution was created in whole or in part by me and I 27 | have the right to submit it under the open source license 28 | indicated in the file; or 29 | 30 | (b) The contribution is based upon previous work that, to the best 31 | of my knowledge, is covered under an appropriate open source 32 | license and I have the right under that license to submit that 33 | work with modifications, whether created in whole or in part 34 | by me, under the same open source license (unless I am 35 | permitted to submit under a different license), as indicated 36 | in the file; or 37 | 38 | (c) The contribution was provided directly to me by some other 39 | person who certified (a), (b) or (c) and I have not modified 40 | it. 41 | 42 | (d) I understand and agree that this project and the contribution 43 | are public and that a record of the contribution (including all 44 | personal information I submit with it, including my sign-off) is 45 | maintained indefinitely and may be redistributed consistent with 46 | this project or the open source license(s) involved. 47 | 48 | then you just add a line saying :: 49 | 50 | Signed-off-by: Random J Developer 51 | 52 | using your real name (sorry, no pseudonyms or anonymous contributions.) 53 | 54 | Git can sign off on your behalf 55 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 56 | 57 | Please note that git makes it trivially easy to sign commits. First, set the 58 | following config options:: 59 | 60 | $ git config --list | grep user 61 | user.email=my_real_email_address@example.com 62 | user.name=My Real Name 63 | 64 | Then just remember to use ``git commit -s``. Git will add the ``Signed-off-by`` 65 | line automatically. 66 | 67 | Separate your changes 68 | --------------------- 69 | 70 | Group *logical changes* into individual commits. 71 | 72 | If you have a series of bulleted modifications, consider separating each of 73 | those into its own commit. 74 | 75 | For example, if your changes include both bug fixes and performance enhancements 76 | for a single component, separate those changes into two or more commits. If your 77 | changes include an API update, and a new feature which uses that new API, 78 | separate those into two patches. 79 | 80 | On the other hand, if you make a single change that affects numerous 81 | files, group those changes into a single commit. Thus a single logical change is 82 | contained within a single patch. (If the change needs to be backported, that 83 | might change the calculus, because smaller commits are easier to backport.) 84 | 85 | Describe your changes 86 | --------------------- 87 | 88 | Each commit has an associated commit message that is stored in git. The first 89 | line of the commit message is the `commit title`_. The second line should be 90 | left blank. The lines that follow constitute the `commit message`_. 91 | 92 | A commit and its message should be focused around a particular change. 93 | 94 | Commit title 95 | ^^^^^^^^^^^^ 96 | 97 | The text up to the first empty line in a commit message is the commit 98 | title. It should be a single short line of at most 72 characters, 99 | summarizing the change, and prefixed with the module you are changing. 100 | Also, it is conventional to use the imperative mood in the commit title. 101 | Positive examples include:: 102 | 103 | wnbd-client: Use libwnbd.dll 104 | 105 | Some negative examples (how *not* to title a commit message):: 106 | 107 | update driver 108 | driver bug fix 109 | fix issue 99999 110 | 111 | Further to the last negative example ("fix issue 99999"), see `Fixes line(s)`_. 112 | 113 | Commit message 114 | ^^^^^^^^^^^^^^ 115 | 116 | (This section is about the body of the commit message. Please also see 117 | the preceding section, `Commit title`_, for advice on titling commit messages.) 118 | 119 | In the body of your commit message, be as specific as possible. If the commit 120 | message title was too short to fully state what the commit is doing, use the 121 | body to explain not just the "what", but also the "why". 122 | 123 | For positive examples, peruse ``git log`` in the ``main`` branch. A negative 124 | example would be a commit message that merely states the obvious. For example: 125 | "this patch includes updates for component X. Please apply." 126 | 127 | Fixes line(s) 128 | ^^^^^^^^^^^^^ 129 | 130 | If the commit fixes one or more issues tracked through Github issues, 131 | add a ``Fixes:`` line (or lines) to the commit message, to connect this change 132 | to addressed issue(s) - for example:: 133 | 134 | Fixes: #15 135 | 136 | This line should be added just before the ``Signed-off-by:`` line (see `Sign 137 | your work`_). 138 | 139 | It helps reviewers to get more context of this bug and facilitates updating of 140 | the issue status. 141 | 142 | Here is an example showing a properly-formed commit message:: 143 | 144 | wnbd-client: add "--foo" option to the bar command 145 | 146 | This commit updates the bar command, adding the "--foo" option. 147 | 148 | Fixes: #45 149 | Signed-off-by: Random J Developer 150 | 151 | If a commit fixes a regression introduced by a different commit, please also 152 | (in addition to the above) add a line referencing the SHA1 of the commit that 153 | introduced the regression. For example:: 154 | 155 | Fixes: 9dbe7a003989f8bb45fe14aaa587e9d60a392727 156 | 157 | PR best practices 158 | ----------------- 159 | 160 | PRs should be opened on branches contained in your fork of 161 | https://github.com/cloudbase/wnbd.git - do not push branches directly to 162 | ``cloudbase/wnbd.git``. 163 | 164 | PRs should target "main". 165 | 166 | In addition to a base, or "target" branch, PRs have several other components: 167 | the `PR title`_, the `PR description`_, labels, comments, etc. Of these, the PR 168 | title and description are relevant for new contributors. 169 | 170 | PR title 171 | ^^^^^^^^ 172 | 173 | If your PR has only one commit, the PR title can be the same as the commit title 174 | (and GitHub will suggest this). If the PR has multiple commits, do not accept 175 | the title GitHub suggest. Either use the title of the most relevant commit, or 176 | write your own title. In the latter case, use the same "module: short 177 | description" convention described in `Commit title`_ for the PR title, with 178 | the following difference: the PR title describes the entire set of changes, 179 | while the `Commit title`_ describes only the changes in a particular commit. 180 | 181 | PR description 182 | ^^^^^^^^^^^^^^ 183 | 184 | In addition to a title, the PR also has a description field, or "body". 185 | 186 | The PR description is a place for summarizing the PR as a whole. It need not 187 | duplicate information that is already in the commit messages. It can contain 188 | notices to maintainers, links to Github issues and other related information, 189 | to-do lists, etc. The PR title and description should give readers a high-level 190 | notion of what the PR is about, quickly enabling them to decide whether they 191 | should take a closer look. 192 | 193 | Test your changes 194 | ----------------- 195 | 196 | Before opening your PR, it's a good idea to run tests on your patchset. 197 | 198 | The most simple test is to verify that your patchset builds, at least in your 199 | own development environment. 200 | 201 | Document your changes 202 | --------------------- 203 | 204 | At the moment, most of the WNBD documentation consists in the readme file. 205 | Please make sure to update it whenever your changes require it. 206 | -------------------------------------------------------------------------------- /driver/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef COMMON_H 8 | #define COMMON_H 1 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /driver/debug.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "debug.h" 12 | #include "options.h" 13 | #include "events.h" 14 | 15 | #define WNBD_LOG_BUFFER_SIZE 512 16 | 17 | VOID EtwPrint(UINT32 Level, 18 | PCHAR FuncName, 19 | UINT32 Line, 20 | PCHAR Buf) 21 | { 22 | BOOLEAN Enabled = WnbdDriverOptions[OptEtwLoggingEnabled].Value.Data.AsBool; 23 | if (!Enabled) { 24 | return; 25 | } 26 | switch (Level) { 27 | case WNBD_LVL_ERROR: 28 | EventWriteErrorEvent(NULL, FuncName, Line, Buf); 29 | break; 30 | case WNBD_LVL_WARN: 31 | EventWriteWarningEvent(NULL, FuncName, Line, Buf); 32 | break; 33 | default: 34 | EventWriteInformationalEvent(NULL, FuncName, Line, Buf); 35 | break; 36 | } 37 | } 38 | 39 | _Use_decl_annotations_ 40 | VOID 41 | WnbdLog(UINT32 Level, 42 | PCHAR FuncName, 43 | UINT32 Line, 44 | PCHAR Format, 45 | ...) 46 | { 47 | UNREFERENCED_PARAMETER(Level); 48 | UNREFERENCED_PARAMETER(FuncName); 49 | UNREFERENCED_PARAMETER(Line); 50 | UNREFERENCED_PARAMETER(Format); 51 | 52 | va_list Args; 53 | CHAR Buf[WNBD_LOG_BUFFER_SIZE]; 54 | 55 | UINT64 WnbdLogLevel = WnbdDriverOptions[OptLogLevel].Value.Data.AsInt64; 56 | if (Level > WnbdLogLevel) { 57 | return; 58 | } 59 | 60 | Buf[0] = 0; 61 | va_start(Args, Format); 62 | RtlStringCbVPrintfA(Buf, sizeof(Buf), Format, Args); 63 | va_end(Args); 64 | 65 | /* Log via ETW */ 66 | EtwPrint(Level, FuncName, Line, Buf); 67 | 68 | /* DbgPrint logging */ 69 | if (WnbdDriverOptions[OptDbgPrintEnabled].Value.Data.AsBool) { 70 | DbgPrintEx(DPFLTR_SCSIMINIPORT_ID, Level, "%s:%lu %s\n", FuncName, Line, Buf); 71 | } 72 | 73 | /* Log via WPP */ 74 | if (WnbdDriverOptions[OptWppLoggingEnabled].Value.Data.AsBool) { 75 | WnbdWppTrace(Level, "%s:%lu %s\n", FuncName, Line, Buf); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /driver/debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef DEBUG_H 8 | #define DEBUG_H 1 9 | 10 | #include 11 | #include 12 | 13 | #define WNBD_LVL_ERROR DPFLTR_ERROR_LEVEL // 0 14 | #define WNBD_LVL_WARN DPFLTR_WARNING_LEVEL // 1 15 | #define WNBD_LVL_INFO DPFLTR_INFO_LEVEL // 3 16 | #define WNBD_LVL_DEBUG (DPFLTR_INFO_LEVEL + 1) // 4 17 | 18 | #ifdef WPPFILE 19 | #define WPPNAME WnbdTraceGuid 20 | #define WPPGUID E35EAF83, 0F07, 418A, 907C, 141CD200F252 21 | 22 | #define WPP_DEFINE_DEFAULT_BITS \ 23 | WPP_DEFINE_BIT(MYDRIVER_ALL_INFO) \ 24 | WPP_DEFINE_BIT(TRACE_KDPRINT) \ 25 | WPP_DEFINE_BIT(DEFAULT_TRACE_LEVEL) 26 | 27 | #define WPP_CONTROL_GUIDS \ 28 | WPP_DEFINE_CONTROL_GUID(WPPNAME,(WPPGUID), \ 29 | WPP_DEFINE_DEFAULT_BITS ) 30 | 31 | #define WPP_FLAGS_LEVEL_LOGGER(Flags, Level) \ 32 | WPP_LEVEL_LOGGER(Flags) 33 | 34 | #define WPP_FLAGS_LEVEL_ENABLED(Flags, Level) \ 35 | (WPP_LEVEL_ENABLED(Flags) && \ 36 | WPP_CONTROL(WPP_BIT_ ## Flags).Level >= Level) 37 | 38 | #include WPPFILE 39 | #else 40 | #define WPP_INIT_TRACING(DriverObject, RegistryPath) 41 | #define WPP_CLEANUP(DriverObject) 42 | #define WnbdWppTrace(...) 43 | #endif 44 | 45 | VOID 46 | WnbdLog(_In_ UINT32 Level, 47 | _In_ PCHAR FuncName, 48 | _In_ UINT32 Line, 49 | _In_ PCHAR Format, ...); 50 | 51 | #define WNBD_LOG_DEBUG(_format, ...) \ 52 | WnbdLog(WNBD_LVL_DEBUG, __FUNCTION__, __LINE__, _format, __VA_ARGS__) 53 | 54 | #define WNBD_LOG_INFO(_format, ...) \ 55 | WnbdLog(WNBD_LVL_INFO, __FUNCTION__, __LINE__, _format, __VA_ARGS__) 56 | 57 | #define WNBD_LOG_ERROR(_format, ...) \ 58 | WnbdLog(WNBD_LVL_ERROR, __FUNCTION__, __LINE__, _format, __VA_ARGS__) 59 | 60 | #define WNBD_LOG_WARN(_format, ...) \ 61 | WnbdLog(WNBD_LVL_WARN, __FUNCTION__, __LINE__, _format, __VA_ARGS__) 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /driver/driver.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "common.h" 8 | #include "debug.h" 9 | #include "driver.h" 10 | #include "scsi_driver_extensions.h" 11 | #include "scsi_trace.h" 12 | #include "userspace.h" 13 | #include "util.h" 14 | #include "options.h" 15 | #include "events.h" 16 | 17 | DRIVER_INITIALIZE DriverEntry; 18 | DRIVER_UNLOAD WnbdDriverUnload; 19 | DRIVER_DISPATCH WnbdDispatchPnp; 20 | PDRIVER_UNLOAD StorPortDriverUnload; 21 | PDRIVER_DISPATCH StorPortDispatchPnp; 22 | 23 | PWNBD_EXTENSION GlobalExt = NULL; 24 | extern HANDLE GlobalDrvRegHandle = NULL; 25 | extern UINT32 GlobalLogLevel = 0; 26 | 27 | _Use_decl_annotations_ 28 | NTSTATUS 29 | DriverEntry(PDRIVER_OBJECT DriverObject, 30 | PUNICODE_STRING RegistryPath) 31 | { 32 | ExInitializeDriverRuntime(DrvRtPoolNxOptIn); 33 | 34 | /* 35 | * Register with ETW 36 | */ 37 | EventRegisterWNBD(); 38 | WPP_INIT_TRACING(DriverObject, RegistryPath); 39 | 40 | /* 41 | * Register Virtual Storport Miniport data 42 | */ 43 | NTSTATUS Status; 44 | HW_INITIALIZATION_DATA WnbdInitData = { 0 }; 45 | WnbdInitData.HwInitializationDataSize = sizeof(HW_INITIALIZATION_DATA); 46 | GlobalLogLevel = 0; 47 | 48 | /* 49 | * Set our SCSI Driver Extensions 50 | */ 51 | WnbdInitData.HwAdapterControl = WnbdHwAdapterControl; 52 | WnbdInitData.HwCompleteServiceIrp = 0; 53 | WnbdInitData.HwFindAdapter = (PVOID)WnbdHwFindAdapter; 54 | WnbdInitData.HwFreeAdapterResources = WnbdHwFreeAdapterResources; 55 | WnbdInitData.HwInitialize = WnbdHwInitialize; 56 | WnbdInitData.HwProcessServiceRequest = WnbdHwProcessServiceRequest; 57 | WnbdInitData.HwResetBus = WnbdHwResetBus; 58 | WnbdInitData.HwStartIo = WnbdHwStartIo; 59 | 60 | WnbdInitData.AdapterInterfaceType = Internal; 61 | WnbdInitData.MultipleRequestPerLu = TRUE; 62 | WnbdInitData.PortVersionFlags = 0; 63 | 64 | WnbdInitData.DeviceExtensionSize = sizeof(WNBD_EXTENSION); 65 | WnbdInitData.SpecificLuExtensionSize = 0; 66 | WnbdInitData.SrbExtensionSize = 0; 67 | 68 | /* 69 | * NOOP for virtual devices 70 | */ 71 | WnbdInitData.HwInterrupt = 0; 72 | WnbdInitData.HwDmaStarted = 0; 73 | WnbdInitData.HwAdapterState = 0; 74 | 75 | WnbdInitData.MapBuffers = STOR_MAP_NON_READ_WRITE_BUFFERS; 76 | WnbdInitData.TaggedQueuing = TRUE; 77 | WnbdInitData.AutoRequestSense = TRUE; 78 | WnbdInitData.MultipleRequestPerLu = TRUE; 79 | 80 | WnbdInitData.FeatureSupport |= 81 | STOR_FEATURE_VIRTUAL_MINIPORT | 82 | STOR_FEATURE_FULL_PNP_DEVICE_CAPABILITIES | 83 | // STOR_FEATURE_DEVICE_NAME_NO_SUFFIX | 84 | STOR_FEATURE_ADAPTER_NOT_REQUIRE_IO_PORT; 85 | WnbdInitData.SrbTypeFlags = SRB_TYPE_FLAG_STORAGE_REQUEST_BLOCK | 86 | SRB_TYPE_FLAG_SCSI_REQUEST_BLOCK; 87 | 88 | Status = IoOpenDriverRegistryKey( 89 | DriverObject, 90 | DriverRegKeyPersistentState, 91 | GENERIC_READ | GENERIC_WRITE, 92 | 0, 93 | &GlobalDrvRegHandle); 94 | if (!NT_SUCCESS(Status)) { 95 | WNBD_LOG_ERROR( 96 | "Couldn't open driver registry key. Status: 0x%x", Status); 97 | } else { 98 | WnbdReloadPersistentOptions(); 99 | } 100 | 101 | /* 102 | * Register our driver 103 | */ 104 | Status = StorPortInitialize(DriverObject, 105 | RegistryPath, 106 | &WnbdInitData, 107 | NULL); 108 | if (!NT_SUCCESS(Status)) { 109 | WNBD_LOG_ERROR("DriverEntry failure in call to StorPortInitialize. Status: 0x%x", Status); 110 | ASSERT(FALSE); 111 | return Status; 112 | } 113 | 114 | /* 115 | * Set up PNP and Unload routines 116 | */ 117 | StorPortDriverUnload = DriverObject->DriverUnload; 118 | DriverObject->DriverUnload = WnbdDriverUnload; 119 | StorPortDispatchPnp = DriverObject->MajorFunction[IRP_MJ_PNP]; 120 | DriverObject->MajorFunction[IRP_MJ_PNP] = 0 != StorPortDispatchPnp ? WnbdDispatchPnp : 0; 121 | GlobalExt = NULL; 122 | 123 | /* 124 | * Report status in upper layers 125 | */ 126 | return Status; 127 | } 128 | 129 | _Use_decl_annotations_ 130 | NTSTATUS 131 | WnbdDispatchPnp(PDEVICE_OBJECT DeviceObject, 132 | PIRP Irp) 133 | { 134 | ASSERT(Irp); 135 | NTSTATUS Status = STATUS_INVALID_DEVICE_REQUEST; 136 | PIO_STACK_LOCATION IoLocation = IoGetCurrentIrpStackLocation(Irp); 137 | SCSI_ADDRESS ScsiAddress = { 0 }; 138 | ASSERT(IoLocation); 139 | UCHAR MinorFunction = IoLocation->MinorFunction; 140 | 141 | WNBD_LOG_DEBUG("Received PnP request: %s (%d).", 142 | WnbdToStringPnpMinorFunction(MinorFunction), MinorFunction); 143 | switch (MinorFunction) { 144 | case IRP_MN_QUERY_CAPABILITIES: 145 | IoLocation->Parameters.DeviceCapabilities.Capabilities->SilentInstall = 1; 146 | // We're disabling SurpriseRemovalOK in order to 147 | // receive device removal PnP events. 148 | IoLocation->Parameters.DeviceCapabilities.Capabilities->SurpriseRemovalOK = 0; 149 | IoLocation->Parameters.DeviceCapabilities.Capabilities->Removable = 1; 150 | IoLocation->Parameters.DeviceCapabilities.Capabilities->EjectSupported = 1; 151 | break; 152 | case IRP_MN_START_DEVICE: 153 | { 154 | if (!GlobalExt) { 155 | WNBD_LOG_DEBUG("IRP_MN_START_DEVICE received but the extension " 156 | "hasn't been initialized."); 157 | break; 158 | } 159 | Status = WnbdGetScsiAddress(DeviceObject, &ScsiAddress); 160 | if (Status) { 161 | WNBD_LOG_ERROR("Could not query SCSI address. Error: %d.", Status); 162 | break; 163 | } 164 | 165 | WNBD_LOG_INFO("Starting device."); 166 | PWNBD_DISK_DEVICE Device = WnbdFindDeviceByAddr( 167 | GlobalExt, ScsiAddress.PathId, 168 | ScsiAddress.TargetId, ScsiAddress.Lun, TRUE); 169 | if (!Device) { 170 | break; 171 | } 172 | Device->PDO = DeviceObject; 173 | 174 | PDEVICE_OBJECT AttachedDisk = IoGetAttachedDeviceReference(DeviceObject); 175 | if (AttachedDisk != DeviceObject) { 176 | Status = WnbdGetDiskNumber( 177 | AttachedDisk, (PULONG) &Device->DiskNumber); 178 | if (Status) { 179 | WNBD_LOG_WARN("Could not get disk number. Error: %d.", 180 | Status); 181 | } 182 | } 183 | else { 184 | WNBD_LOG_WARN("Couldn't not get disk number. " 185 | "Couldn't get attached PDO."); 186 | } 187 | ObDereferenceObject(AttachedDisk); 188 | 189 | DWORD RequiredSize = 0; 190 | Status = WnbdGetDiskInstancePath( 191 | DeviceObject, Device->PNPDeviceID, 192 | sizeof(Device->PNPDeviceID), 193 | &RequiredSize); 194 | if (Status) { 195 | WNBD_LOG_WARN("Couldn't get PNP device id. Error: %d", Status); 196 | } 197 | 198 | WnbdReleaseDevice(Device); 199 | } 200 | break; 201 | // We won't remove the device upon receiving IRP_MN_QUERY_REMOVE_DEVICE. 202 | // The device removal might be vetoed by other parts of the storage stack, 203 | // so we'd affect soft removals. The only downside is that if the remove 204 | // gets vetoed, uninstalling the driver will require a reboot. 205 | case IRP_MN_QUERY_REMOVE_DEVICE: 206 | { 207 | if (NULL == GlobalExt || !GlobalExt->DeviceCount) { 208 | break; 209 | } 210 | Status = WnbdGetScsiAddress(DeviceObject, &ScsiAddress); 211 | if (Status) { 212 | WNBD_LOG_WARN("Could not query SCSI address. Error: 0x%x.", Status); 213 | break; 214 | } 215 | 216 | WNBD_LOG_DEBUG("Device about to be removed, " 217 | "checking for pending requests"); 218 | PWNBD_DISK_DEVICE Device = WnbdFindDeviceByAddr( 219 | GlobalExt, ScsiAddress.PathId, 220 | ScsiAddress.TargetId, ScsiAddress.Lun, TRUE); 221 | if (!Device) { 222 | WNBD_LOG_DEBUG("Device already removed."); 223 | break; 224 | } 225 | char InstanceName[256] = { 0 }; 226 | // Copy InstanceName to a local buffer in order to log it 227 | // without accessing the released device 228 | RtlCopyMemory(&InstanceName, &Device->Properties.InstanceName, 229 | strlen(Device->Properties.InstanceName)); 230 | 231 | if (Device->PDO != DeviceObject) { 232 | WNBD_LOG_INFO( 233 | "Different device found at the specified address. " 234 | "The requested device might've been removed already."); 235 | WnbdReleaseDevice(Device); 236 | break; 237 | } 238 | BOOLEAN HasPendingRequests = HasPendingAsyncRequests(Device); 239 | WnbdReleaseDevice(Device); 240 | if (HasPendingRequests) { 241 | Status = STATUS_DEVICE_BUSY; 242 | WNBD_LOG_WARN("Device removal requested while having pending IO, " 243 | "reporting device busy: %s", InstanceName); 244 | Irp->IoStatus.Status = Status; 245 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 246 | WNBD_LOG_DEBUG("Exit: 0x%x", Status); 247 | return Status; 248 | } else { 249 | WNBD_LOG_INFO("Device removal requested, no pending IO: %s", 250 | InstanceName); 251 | } 252 | } 253 | break; 254 | case IRP_MN_REMOVE_DEVICE: 255 | { 256 | if (NULL == GlobalExt || !GlobalExt->DeviceCount) { 257 | break; 258 | } 259 | Status = WnbdGetScsiAddress(DeviceObject, &ScsiAddress); 260 | if (Status) { 261 | WNBD_LOG_WARN("Could not query SCSI address. Error: 0x%x.", Status); 262 | break; 263 | } 264 | 265 | WNBD_LOG_DEBUG("Removing disk device."); 266 | PWNBD_DISK_DEVICE Device = WnbdFindDeviceByAddr( 267 | GlobalExt, ScsiAddress.PathId, 268 | ScsiAddress.TargetId, ScsiAddress.Lun, TRUE); 269 | if (!Device) { 270 | WNBD_LOG_DEBUG("Device already removed."); 271 | break; 272 | } 273 | char InstanceName[256] = { 0 }; 274 | // Copy InstanceName to a local buffer in order to log it 275 | // without accessing the released device 276 | RtlCopyMemory(&InstanceName, &Device->Properties.InstanceName, 277 | strlen(Device->Properties.InstanceName)); 278 | 279 | if (Device->PDO != DeviceObject) { 280 | WNBD_LOG_INFO( 281 | "Different device found at the specified address. " 282 | "The requested device might've been removed already."); 283 | WnbdReleaseDevice(Device); 284 | break; 285 | } 286 | WNBD_LOG_INFO("Disconnecting disk: %s.", InstanceName); 287 | 288 | WnbdDisconnectSync(Device); 289 | 290 | WNBD_LOG_INFO("Successfully disconnected disk: %s", InstanceName); 291 | } 292 | break; 293 | } 294 | 295 | Status = StorPortDispatchPnp(DeviceObject, Irp); 296 | 297 | WNBD_LOG_DEBUG("Exit: 0x%x", Status); 298 | return Status; 299 | } 300 | 301 | _Use_decl_annotations_ 302 | VOID 303 | WnbdDriverUnload(PDRIVER_OBJECT DriverObject) 304 | { 305 | 306 | if (0 != StorPortDriverUnload) { 307 | StorPortDriverUnload(DriverObject); 308 | } 309 | 310 | /* 311 | * Unregister from ETW 312 | */ 313 | EventUnregisterWNBD(); 314 | WPP_CLEANUP(DriverObject); 315 | if (GlobalDrvRegHandle) { 316 | ZwClose(GlobalDrvRegHandle); 317 | GlobalDrvRegHandle = NULL; 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /driver/driver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef DRIVER_H 8 | #define DRIVER_H 1 9 | 10 | #include "version.h" 11 | 12 | // Maximum number of scsi targets per bus 13 | #define WNBD_MAX_TARGETS_PER_BUS 128 14 | // Maximum number of luns per target 15 | #define WNBD_MAX_LUNS_PER_TARGET 1 16 | // Maximum number of buses per target 17 | #define WNBD_MAX_BUSES_PER_ADAPTER 1 18 | // The maximum number of disks per WNBD adapter 19 | #define WNBD_MAX_NUMBER_OF_DISKS \ 20 | (WNBD_MAX_LUNS_PER_TARGET * \ 21 | WNBD_MAX_TARGETS_PER_BUS * \ 22 | WNBD_MAX_BUSES_PER_ADAPTER) 23 | 24 | #define WNBD_INQUIRY_VENDOR_ID "WNBD" 25 | #define WNBD_INQUIRY_PRODUCT_ID "WNBD_DISK" 26 | // Placeholder 27 | #define WNBD_INQUIRY_VENDOR_SPECIFIC "" 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /driver/options.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "options.h" 8 | #include "debug.h" 9 | #include "util.h" 10 | #include "wnbd_ioctl.h" 11 | 12 | #pragma warning(push) 13 | #pragma warning(disable:4204) 14 | 15 | extern HANDLE GlobalDrvRegHandle; 16 | 17 | #define WNBD_DEF_OPT(OptName, TypeSuffix, DefaultVal) \ 18 | {.Name = OptName, \ 19 | .Type = WnbdOpt ## TypeSuffix, \ 20 | .Default = { .Type = WnbdOpt ## TypeSuffix, \ 21 | .Data.As ## TypeSuffix = DefaultVal}, \ 22 | .Value = { .Type = WnbdOpt ## TypeSuffix, \ 23 | .Data.As ## TypeSuffix = DefaultVal}, \ 24 | } 25 | 26 | // Make sure to update WNBD_OPT_KEY whenever adding or removing options. 27 | // It provides simple and fast access to the options without having to 28 | // introduce other structures. Also, it would be nice to keep the 29 | // options sorted. 30 | WNBD_OPTION WnbdDriverOptions[] = { 31 | WNBD_DEF_OPT(L"LogLevel", Int64, WNBD_LVL_WARN), 32 | WNBD_DEF_OPT(L"NewMappingsAllowed", Bool, TRUE), 33 | WNBD_DEF_OPT(L"EtwLoggingEnabled", Bool, TRUE), 34 | WNBD_DEF_OPT(L"WppLoggingEnabled", Bool, FALSE), 35 | WNBD_DEF_OPT(L"DbgPrintEnabled", Bool, TRUE), 36 | WNBD_DEF_OPT(L"MaxIOReqPerAdapter", Int64, WNBD_DEFAULT_MAX_IO_REQ_PER_ADAPTER), 37 | WNBD_DEF_OPT(L"MaxIOReqPerLun", Int64, WNBD_DEFAULT_MAX_IO_REQ_PER_LUN), 38 | WNBD_DEF_OPT(L"RemoveStaleConnections", Bool, TRUE), 39 | WNBD_DEF_OPT(L"StaleReqTimeoutMs", Int64, WNBD_DEFAULT_STALE_REQ_TIMEOUT_MS), 40 | WNBD_DEF_OPT(L"StaleConnTimeoutMs", Int64, WNBD_DEFAULT_STALE_CONN_TIMEOUT_MS), 41 | }; 42 | DWORD WnbdOptionsCount = sizeof(WnbdDriverOptions) / sizeof(WNBD_OPTION); 43 | 44 | PWNBD_OPTION WnbdFindOpt(PWCHAR Name) 45 | { 46 | for (DWORD i = 0; i < WnbdOptionsCount; i++) { 47 | if (!_wcsicmp(Name, WnbdDriverOptions[i].Name)) { 48 | return &WnbdDriverOptions[i]; 49 | } 50 | } 51 | return NULL; 52 | } 53 | 54 | _Use_decl_annotations_ 55 | NTSTATUS WnbdGetPersistentOpt( 56 | PWCHAR Name, 57 | PWNBD_OPTION_VALUE Value) 58 | { 59 | WNBD_LOG_DEBUG("Retrieving persistent opt: %ls.", Name); 60 | PWNBD_OPTION Option = WnbdFindOpt(Name); 61 | if (!Option) { 62 | WNBD_LOG_WARN("Could not find option: %ls.", Name); 63 | return STATUS_OBJECT_NAME_NOT_FOUND; 64 | } 65 | 66 | RtlZeroMemory(&Value->Data, sizeof(Value->Data)); 67 | 68 | BYTE Buff[ 69 | sizeof(KEY_VALUE_PARTIAL_INFORMATION) + 70 | (WNBD_MAX_NAME_LENGTH * sizeof(WCHAR))] = { 0 }; 71 | PKEY_VALUE_PARTIAL_INFORMATION ValueInformation = ( 72 | PKEY_VALUE_PARTIAL_INFORMATION) Buff; 73 | 74 | UNICODE_STRING KeyName = { 0 }; 75 | RtlInitUnicodeString(&KeyName, Name); 76 | ULONG RequiredBufferSize = 0; 77 | NTSTATUS Status = ZwQueryValueKey( 78 | GlobalDrvRegHandle, &KeyName, KeyValuePartialInformation, 79 | ValueInformation, 80 | sizeof(Buff), &RequiredBufferSize); 81 | if (Status) { 82 | WNBD_LOG_WARN( 83 | "Couldn't retrieve registry key: %ls. Status: %d", 84 | Name, Status); 85 | return Status; 86 | } 87 | 88 | if (WnbdOptRegType(Option->Type) != ValueInformation->Type) { 89 | WNBD_LOG_WARN( 90 | "Registry value type mismatch: %ls. " 91 | "Expecting: %d, retrieved: %d", 92 | Name, Option->Type, ValueInformation->Type); 93 | return STATUS_OBJECT_TYPE_MISMATCH; 94 | } 95 | 96 | switch(Option->Type) { 97 | case WnbdOptBool: 98 | // We're storing bool and int64 values as QWORD 99 | if (ValueInformation->DataLength != sizeof(UINT64)) { 100 | WNBD_LOG_WARN( 101 | "Registry value size mismatch: %ls. " 102 | "Expecting: %d, actual: %d", 103 | Name, sizeof(UINT64), ValueInformation->DataLength); 104 | return STATUS_OBJECT_TYPE_MISMATCH; 105 | } 106 | Value->Data.AsBool = !!*(PUINT64)ValueInformation->Data; 107 | break; 108 | case WnbdOptInt64: 109 | if (ValueInformation->DataLength != sizeof(UINT64)) { 110 | WNBD_LOG_WARN( 111 | "Registry value size mismatch: %ls. " 112 | "Expecting: %d, actual: %d", 113 | Name, sizeof(UINT64), ValueInformation->DataLength); 114 | return STATUS_OBJECT_TYPE_MISMATCH; 115 | } 116 | Value->Data.AsInt64 = *(PUINT64)ValueInformation->Data; 117 | break; 118 | case WnbdOptWstr: 119 | if (ValueInformation->DataLength > sizeof(Value->Data.AsWstr)) { 120 | WNBD_LOG_WARN( 121 | "Registry value size overflow: %ls. " 122 | "Maximum allowed: %d, actual: %d", 123 | Name, 124 | sizeof(Value->Data.AsWstr), ValueInformation->DataLength); 125 | return STATUS_BUFFER_OVERFLOW; 126 | } 127 | RtlCopyMemory(Value->Data.AsWstr, ValueInformation->Data, 128 | ValueInformation->DataLength); 129 | break; 130 | default: 131 | WNBD_LOG_WARN( 132 | "Unsupported option type: %d. " 133 | "Key: %ls.", Option->Type, Name); 134 | return STATUS_OBJECT_TYPE_MISMATCH; 135 | } 136 | 137 | WNBD_LOG_DEBUG("Retrieved persistent option: %ls.", Name); 138 | Value->Type = Option->Type; 139 | return STATUS_SUCCESS; 140 | } 141 | 142 | _Use_decl_annotations_ 143 | NTSTATUS WnbdGetDrvOpt( 144 | PWCHAR Name, 145 | PWNBD_OPTION_VALUE Value, 146 | BOOLEAN Persistent) 147 | { 148 | if (Persistent) { 149 | return WnbdGetPersistentOpt(Name, Value); 150 | } 151 | 152 | PWNBD_OPTION Option = WnbdFindOpt(Name); 153 | if (!Option) { 154 | WNBD_LOG_WARN("Could not find option: %ls.", Name); 155 | return STATUS_OBJECT_NAME_NOT_FOUND; 156 | } 157 | 158 | *Value = Option->Value; 159 | return STATUS_SUCCESS; 160 | } 161 | 162 | _Use_decl_annotations_ 163 | NTSTATUS WnbdProcessOptionValue( 164 | PWNBD_OPTION Option, 165 | PWNBD_OPTION_VALUE Value) 166 | { 167 | if (Value->Type == WnbdOptWstr) { 168 | // Ensure that the string is NULL terminated. 169 | Value->Data.AsWstr[WNBD_MAX_NAME_LENGTH - 1] = L'\0'; 170 | } 171 | 172 | NTSTATUS Status = STATUS_SUCCESS; 173 | if (Value->Type != Option->Type) { 174 | // We'll try to convert strings, rejecting other 175 | // type mismatches. 176 | if (Value->Type != WnbdOptWstr) { 177 | return STATUS_OBJECT_TYPE_MISMATCH; 178 | } 179 | 180 | WNBD_OPTION_VALUE ConvertedValue = { .Type = Option->Type }; 181 | UNICODE_STRING StringBuff = { 182 | 0, 183 | sizeof(Option->Value), 184 | (PWCHAR) &Value->Data.AsWstr}; 185 | switch (Option->Type) { 186 | case WnbdOptBool: 187 | Status = WstrToBool(Value->Data.AsWstr, &ConvertedValue.Data.AsBool); 188 | break; 189 | case WnbdOptInt64: 190 | Status = RtlUnicodeStringToInt64( 191 | &StringBuff, 0, 192 | &ConvertedValue.Data.AsInt64, NULL); 193 | break; 194 | default: 195 | WNBD_LOG_WARN("Unsupported option type: %d.", Option->Type); 196 | Status = STATUS_OBJECT_TYPE_MISMATCH; 197 | break; 198 | } 199 | if (!Status) { 200 | *Value = ConvertedValue; 201 | } 202 | } 203 | return Status; 204 | } 205 | 206 | _Use_decl_annotations_ 207 | NTSTATUS WnbdSetDrvOpt( 208 | PWCHAR Name, 209 | PWNBD_OPTION_VALUE Value, 210 | BOOLEAN Persistent) 211 | { 212 | PWNBD_OPTION Option = WnbdFindOpt(Name); 213 | if (!Option) { 214 | WNBD_LOG_WARN("Could not find option: %ls.", Name); 215 | return STATUS_OBJECT_NAME_NOT_FOUND; 216 | } 217 | 218 | NTSTATUS Status = WnbdProcessOptionValue(Option, Value); 219 | if (Status) { 220 | return Status; 221 | } 222 | 223 | // We'll set the persistent value first. If that fails, 224 | // we won't set the runtime value. 225 | if (Persistent) { 226 | UNICODE_STRING KeyName = { 0 }; 227 | RtlInitUnicodeString(&KeyName, Name); 228 | Status = ZwSetValueKey( 229 | GlobalDrvRegHandle, 230 | &KeyName, 231 | 0, 232 | WnbdOptRegType(Option->Type), 233 | (PVOID)&Value->Data, 234 | WnbdOptRegSize(Option->Type)); 235 | if (Status) { 236 | WNBD_LOG_ERROR("Couln't set registry key. Status: %d", Status); 237 | return Status; 238 | } 239 | } 240 | 241 | Option->Value = *Value; 242 | return STATUS_SUCCESS; 243 | } 244 | 245 | _Use_decl_annotations_ 246 | NTSTATUS WnbdResetDrvOpt( 247 | PWCHAR Name, 248 | BOOLEAN Persistent) 249 | { 250 | PWNBD_OPTION Option = WnbdFindOpt(Name); 251 | if (!Option) { 252 | WNBD_LOG_WARN("Could not find opton: %ls.", Name); 253 | return STATUS_OBJECT_NAME_NOT_FOUND; 254 | } 255 | 256 | if (Persistent) { 257 | UNICODE_STRING KeyName = { 0 }; 258 | RtlInitUnicodeString(&KeyName, Name); 259 | NTSTATUS Status = ZwDeleteValueKey( 260 | GlobalDrvRegHandle, 261 | &KeyName); 262 | if (Status) { 263 | WNBD_LOG_ERROR("Couln't remove registry key. Status: %d", Status); 264 | return Status; 265 | } 266 | } 267 | 268 | Option->Value = Option->Default; 269 | return STATUS_SUCCESS; 270 | } 271 | 272 | _Use_decl_annotations_ 273 | NTSTATUS WnbdReloadPersistentOptions() 274 | { 275 | DWORD Status = STATUS_SUCCESS; 276 | 277 | for (DWORD i = 0; i < WnbdOptionsCount; i++) { 278 | PWNBD_OPTION Option = &WnbdDriverOptions[i]; 279 | WNBD_OPTION_VALUE PersistentValue = { 0 }; 280 | Status = WnbdGetPersistentOpt(Option->Name, &PersistentValue); 281 | if (Status) { 282 | if (Status != STATUS_OBJECT_NAME_NOT_FOUND) { 283 | WNBD_LOG_WARN("Could not load option %ls. Error: 0x%x", 284 | Option->Name, Status); 285 | } 286 | continue; 287 | } 288 | Status = WnbdProcessOptionValue(Option, &PersistentValue); 289 | if (Status) { 290 | WNBD_LOG_WARN("Could not process option %ls. Error: 0x%x", 291 | Option->Name, Status); 292 | continue; 293 | } 294 | 295 | Option->Value = PersistentValue; 296 | } 297 | 298 | return Status; 299 | } 300 | 301 | _Use_decl_annotations_ 302 | NTSTATUS 303 | WnbdListDrvOpt( 304 | PWNBD_OPTION_LIST OptionList, 305 | PULONG BufferSize, 306 | BOOLEAN Persistent) 307 | { 308 | ASSERT(OptionList); 309 | 310 | DWORD RequiredBuffSize = 311 | sizeof(WNBD_OPTION) * WnbdOptionsCount + sizeof(WNBD_OPTION_LIST); 312 | if (*BufferSize < RequiredBuffSize) { 313 | *BufferSize = RequiredBuffSize; 314 | return STATUS_BUFFER_TOO_SMALL; 315 | } 316 | 317 | OptionList->Count = 0; 318 | for (DWORD i = 0; i < WnbdOptionsCount; i++) { 319 | // When persistent options are requested, we'll only retrieve 320 | // the ones that are currently set. 321 | if (Persistent) { 322 | WNBD_OPTION_VALUE PersistentValue = { 0 }; 323 | DWORD Status = WnbdGetPersistentOpt( 324 | WnbdDriverOptions[i].Name, 325 | &PersistentValue); 326 | if (!Status) { 327 | OptionList->Options[OptionList->Count] = WnbdDriverOptions[i]; 328 | OptionList->Options[OptionList->Count].Value = PersistentValue; 329 | OptionList->Count += 1; 330 | } 331 | } 332 | else { 333 | OptionList->Options[i] = WnbdDriverOptions[i]; 334 | OptionList->Count += 1; 335 | } 336 | } 337 | 338 | return STATUS_SUCCESS; 339 | } 340 | 341 | #pragma warning(pop) 342 | -------------------------------------------------------------------------------- /driver/options.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "common.h" 10 | #include "wnbd_ioctl.h" 11 | 12 | static inline UCHAR WnbdOptRegType(WnbdOptValType Type) { 13 | switch(Type) { 14 | case WnbdOptBool: 15 | case WnbdOptInt64: 16 | return REG_QWORD; 17 | case WnbdOptWstr: 18 | return REG_SZ; 19 | default: 20 | return REG_NONE; 21 | } 22 | } 23 | 24 | static inline DWORD WnbdOptRegSize(WnbdOptValType Type) { 25 | switch(Type) { 26 | case WnbdOptBool: 27 | case WnbdOptInt64: 28 | return 8; 29 | case WnbdOptWstr: 30 | return WNBD_MAX_NAME_LENGTH; 31 | default: 32 | return 0; 33 | } 34 | } 35 | 36 | typedef enum { 37 | OptLogLevel, 38 | OptNewMappingsAllowed, 39 | OptEtwLoggingEnabled, 40 | OptWppLoggingEnabled, 41 | OptDbgPrintEnabled, 42 | OptMaxIOReqPerAdapter, 43 | OptMaxIOReqPerLun, 44 | OptRemoveStaleConnections, 45 | OptStaleReqTimeoutMs, 46 | OptStaleConnTimeoutMs, 47 | } WNBD_OPT_KEY; 48 | 49 | extern WNBD_OPTION WnbdDriverOptions[]; 50 | 51 | _IRQL_requires_(PASSIVE_LEVEL) 52 | NTSTATUS WnbdGetPersistentOpt( 53 | PWCHAR Name, 54 | PWNBD_OPTION_VALUE Value); 55 | 56 | _IRQL_requires_(PASSIVE_LEVEL) 57 | NTSTATUS WnbdGetDrvOpt( 58 | PWCHAR Name, 59 | PWNBD_OPTION_VALUE Value, 60 | BOOLEAN Persistent); 61 | 62 | _IRQL_requires_(PASSIVE_LEVEL) 63 | NTSTATUS WnbdSetDrvOpt( 64 | PWCHAR Name, 65 | PWNBD_OPTION_VALUE Value, 66 | BOOLEAN Persistent); 67 | 68 | _IRQL_requires_(PASSIVE_LEVEL) 69 | NTSTATUS WnbdResetDrvOpt( 70 | PWCHAR Name, 71 | BOOLEAN Persistent); 72 | 73 | _IRQL_requires_(PASSIVE_LEVEL) 74 | NTSTATUS WnbdReloadPersistentOptions(); 75 | 76 | _IRQL_requires_(PASSIVE_LEVEL) 77 | NTSTATUS WnbdListDrvOpt( 78 | PWNBD_OPTION_LIST OptionList, 79 | PULONG BufferSize, 80 | BOOLEAN Persistent); 81 | 82 | // Validate the option value and perform 83 | // required conversions. 84 | _IRQL_requires_(PASSIVE_LEVEL) 85 | NTSTATUS WnbdProcessOptionValue( 86 | PWNBD_OPTION Option, 87 | PWNBD_OPTION_VALUE Value); 88 | -------------------------------------------------------------------------------- /driver/scsi_driver_extensions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef SCSI_DRIVER_EXTENSIONS_H 8 | #define SCSI_DRIVER_EXTENSIONS_H 1 9 | 10 | #include "common.h" 11 | #include "wnbd_ioctl.h" 12 | 13 | typedef struct _WNBD_EXTENSION { 14 | UNICODE_STRING DeviceInterface; 15 | LIST_ENTRY DeviceList; 16 | KSPIN_LOCK DeviceListLock; 17 | LONG DeviceCount; 18 | ERESOURCE DeviceCreationLock; 19 | 20 | EX_RUNDOWN_REF RundownProtection; 21 | KEVENT GlobalDeviceRemovalEvent; 22 | } WNBD_EXTENSION, *PWNBD_EXTENSION; 23 | 24 | typedef struct _WNBD_DISK_DEVICE 25 | { 26 | LIST_ENTRY ListEntry; 27 | PWNBD_EXTENSION DeviceExtension; 28 | 29 | BOOLEAN Connected; 30 | WNBD_PROPERTIES Properties; 31 | WNBD_CONNECTION_ID ConnectionId; 32 | 33 | USHORT Bus; 34 | USHORT Target; 35 | USHORT Lun; 36 | 37 | INT DiskNumber; 38 | WCHAR PNPDeviceID[WNBD_MAX_NAME_LENGTH]; 39 | PDEVICE_OBJECT PDO; 40 | 41 | PINQUIRYDATA InquiryData; 42 | 43 | LIST_ENTRY PendingReqListHead; 44 | KSPIN_LOCK PendingReqListLock; 45 | 46 | LIST_ENTRY SubmittedReqListHead; 47 | KSPIN_LOCK SubmittedReqListLock; 48 | 49 | KSEMAPHORE DeviceEvent; 50 | PVOID DeviceMonitorThread; 51 | BOOLEAN HardRemoveDevice; 52 | KEVENT DeviceRemovalEvent; 53 | // The rundown protection provides device reference counting, preventing 54 | // it from being deallocated while still being accessed. This is 55 | // especially important for IO dispatching. 56 | EX_RUNDOWN_REF RundownProtection; 57 | 58 | WNBD_DRV_STATS Stats; 59 | } WNBD_DISK_DEVICE, *PWNBD_DISK_DEVICE; 60 | 61 | typedef struct _SRB_QUEUE_ELEMENT { 62 | LIST_ENTRY Link; 63 | PVOID Srb; 64 | UINT64 StartingLbn; 65 | ULONG DataLength; 66 | BOOLEAN FUA; 67 | PVOID DeviceExtension; 68 | UINT64 Tag; 69 | BOOLEAN Aborted; 70 | BOOLEAN Completed; 71 | // Retrieved using KeQueryInterruptTime. 72 | UINT64 ReqTimestamp; 73 | } SRB_QUEUE_ELEMENT, * PSRB_QUEUE_ELEMENT; 74 | 75 | SCSI_ADAPTER_CONTROL_STATUS 76 | WnbdHwAdapterControl(_In_ PVOID DeviceExtension, 77 | _In_ SCSI_ADAPTER_CONTROL_TYPE ControlType, 78 | _In_ PVOID Parameters); 79 | 80 | ULONG 81 | WnbdHwFindAdapter(_In_ PVOID DeviceExtension, 82 | _In_ PVOID HwContext, 83 | _In_ PVOID BusInformation, 84 | _In_ PVOID LowerDevice, 85 | _In_ PCHAR ArgumentString, 86 | _Inout_ PPORT_CONFIGURATION_INFORMATION ConfigInfo, 87 | _In_ PBOOLEAN Again); 88 | 89 | VOID 90 | WnbdHwFreeAdapterResources(_In_ PVOID DeviceExtension); 91 | 92 | BOOLEAN 93 | WnbdHwInitialize(_In_ PVOID DeviceExtension); 94 | 95 | VOID 96 | WnbdHwProcessServiceRequest(_In_ PVOID DeviceExtension, 97 | _In_ PVOID Irp); 98 | 99 | BOOLEAN 100 | WnbdHwResetBus(_In_ PVOID DeviceExtension, 101 | _In_ ULONG PathId); 102 | 103 | BOOLEAN 104 | WnbdHwStartIo(_In_ PVOID PDevExt, 105 | _In_ PVOID PSrb); 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /driver/scsi_function.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "common.h" 8 | #include "debug.h" 9 | #include "scsi_driver_extensions.h" 10 | #include "scsi_operation.h" 11 | #include "scsi_trace.h" 12 | #include "srb_helper.h" 13 | #include "scsi_function.h" 14 | #include "util.h" 15 | #include "userspace.h" 16 | 17 | UCHAR DrainDeviceQueues(PVOID DeviceExtension, 18 | PVOID Srb, 19 | BOOLEAN CheckStaleConn) 20 | 21 | { 22 | ASSERT(Srb); 23 | ASSERT(DeviceExtension); 24 | 25 | UCHAR SrbStatus = SRB_STATUS_NO_DEVICE; 26 | PWNBD_DISK_DEVICE Device; 27 | 28 | UCHAR PathId = SrbGetPathId(Srb); 29 | UCHAR TargetId = SrbGetTargetId(Srb); 30 | UCHAR Lun = SrbGetLun(Srb); 31 | 32 | PCDB Cdb = SrbGetCdb(Srb); 33 | if (Cdb) { 34 | BYTE CdbValue = Cdb->AsByte[0]; 35 | 36 | WNBD_LOG_INFO("Received %#02x command. SRB = 0x%p. CDB = 0x%x. PathId: %d TargetId: %d LUN: %d", 37 | CdbValue, Srb, CdbValue, PathId, TargetId, Lun); 38 | } 39 | 40 | Device = WnbdFindDeviceByAddr( 41 | DeviceExtension, PathId, TargetId, Lun, TRUE); 42 | if (NULL == Device) { 43 | WNBD_LOG_INFO("Could not find device PathId: %d TargetId: %d LUN: %d", 44 | PathId, TargetId, Lun); 45 | goto Exit; 46 | } 47 | 48 | DrainDeviceQueue(Device, FALSE, CheckStaleConn); 49 | DrainDeviceQueue(Device, TRUE, CheckStaleConn); 50 | 51 | WnbdReleaseDevice(Device); 52 | SrbStatus = SRB_STATUS_SUCCESS; 53 | 54 | Exit: 55 | return SrbStatus; 56 | } 57 | 58 | _Use_decl_annotations_ 59 | UCHAR 60 | WnbdAbortFunction(_In_ PVOID DeviceExtension, 61 | _In_ PVOID Srb) 62 | { 63 | ASSERT(Srb); 64 | ASSERT(DeviceExtension); 65 | 66 | UCHAR SrbStatus = DrainDeviceQueues(DeviceExtension, Srb, TRUE); 67 | 68 | return SrbStatus; 69 | } 70 | 71 | _Use_decl_annotations_ 72 | UCHAR 73 | WnbdResetLogicalUnitFunction(PVOID DeviceExtension, 74 | PVOID Srb) 75 | { 76 | ASSERT(Srb); 77 | ASSERT(DeviceExtension); 78 | 79 | UCHAR SrbStatus = DrainDeviceQueues(DeviceExtension, Srb, TRUE); 80 | 81 | return SrbStatus; 82 | } 83 | 84 | _Use_decl_annotations_ 85 | UCHAR 86 | WnbdResetDeviceFunction(PVOID DeviceExtension, 87 | PVOID Srb) 88 | { 89 | ASSERT(Srb); 90 | ASSERT(DeviceExtension); 91 | 92 | StorPortCompleteRequest(DeviceExtension, 93 | SrbGetPathId(Srb), 94 | SrbGetTargetId(Srb), 95 | SrbGetLun(Srb), 96 | SRB_STATUS_TIMEOUT); 97 | 98 | return SRB_STATUS_SUCCESS; 99 | } 100 | 101 | _Use_decl_annotations_ 102 | UCHAR 103 | WnbdExecuteScsiFunction(PVOID DeviceExtension, 104 | PVOID Srb, 105 | PBOOLEAN Complete) 106 | { 107 | ASSERT(DeviceExtension); 108 | ASSERT(Srb); 109 | ASSERT(Complete); 110 | 111 | UCHAR PathId = SrbGetPathId(Srb); 112 | UCHAR TargetId = SrbGetTargetId(Srb); 113 | UCHAR Lun = SrbGetLun(Srb); 114 | 115 | NTSTATUS Status = STATUS_SUCCESS; 116 | UCHAR SrbStatus = SRB_STATUS_NO_DEVICE; 117 | PWNBD_DISK_DEVICE Device; 118 | *Complete = TRUE; 119 | 120 | PCDB Cdb = SrbGetCdb(Srb); 121 | if (Cdb) { 122 | BYTE CdbValue = Cdb->AsByte[0]; 123 | 124 | WNBD_LOG_DEBUG("Received %#02x command. SRB = 0x%p. CDB = 0x%x. PathId: %d TargetId: %d LUN: %d", 125 | CdbValue, Srb, CdbValue, PathId, TargetId, Lun); 126 | } 127 | 128 | Device = WnbdFindDeviceByAddr( 129 | (PWNBD_EXTENSION)DeviceExtension, PathId, TargetId, Lun, TRUE); 130 | if (NULL == Device) { 131 | WNBD_LOG_DEBUG("Could not find device PathId: %d TargetId: %d LUN: %d", 132 | PathId, TargetId, Lun); 133 | goto Exit; 134 | } 135 | if (Device->HardRemoveDevice) { 136 | WNBD_LOG_DEBUG("%p is marked for deletion. PathId = %d. TargetId = %d. LUN = %d", 137 | Device, PathId, TargetId, Lun); 138 | goto Exit; 139 | } 140 | 141 | InterlockedIncrement64(&Device->Stats.OutstandingIOCount); 142 | Status = WnbdHandleSrbOperation((PWNBD_EXTENSION)DeviceExtension, Device, Srb); 143 | 144 | if(STATUS_PENDING == Status) { 145 | *Complete = FALSE; 146 | SrbStatus = SRB_STATUS_PENDING; 147 | } else { 148 | InterlockedDecrement64(&Device->Stats.OutstandingIOCount); 149 | SrbStatus = SrbGetSrbStatus(Srb); 150 | } 151 | 152 | Exit: 153 | if (Device) 154 | WnbdReleaseDevice(Device); 155 | 156 | return SrbStatus; 157 | } 158 | 159 | _Use_decl_annotations_ 160 | UCHAR 161 | WnbdPNPFunction(PVOID Srb) 162 | { 163 | ASSERT(Srb); 164 | 165 | STOR_PNP_ACTION PnPAction; 166 | ULONG SrbPnPFlags; 167 | 168 | PSRBEX_DATA_PNP SrbExPnp = (PSRBEX_DATA_PNP)SrbGetSrbExDataByType( 169 | (PSTORAGE_REQUEST_BLOCK) Srb, 170 | SrbExDataTypePnP); 171 | if (SrbExPnp) { 172 | SrbPnPFlags = SrbExPnp->SrbPnPFlags; 173 | PnPAction = SrbExPnp->PnPAction; 174 | } else { 175 | PSCSI_PNP_REQUEST_BLOCK SrbPnp = (PSCSI_PNP_REQUEST_BLOCK) Srb; 176 | SrbPnPFlags = SrbPnp->SrbPnPFlags; 177 | PnPAction = SrbPnp->PnPAction; 178 | } 179 | 180 | UCHAR SrbStatus = SRB_STATUS_INVALID_REQUEST; 181 | 182 | switch (PnPAction) 183 | { 184 | case StorQueryCapabilities: 185 | if (!(SrbPnPFlags & SRB_PNP_FLAGS_ADAPTER_REQUEST) && 186 | SrbGetDataTransferLength(Srb) >= 187 | sizeof(STOR_DEVICE_CAPABILITIES_EX)) 188 | { 189 | PVOID DataBuffer = SrbGetDataBuffer(Srb); 190 | ASSERT(DataBuffer); 191 | 192 | PSTOR_DEVICE_CAPABILITIES_EX DeviceCapabilitiesEx = DataBuffer; 193 | RtlZeroMemory( 194 | DeviceCapabilitiesEx, 195 | sizeof(STOR_DEVICE_CAPABILITIES_EX)); 196 | DeviceCapabilitiesEx->DefaultWriteCacheEnabled = 1; 197 | DeviceCapabilitiesEx->SilentInstall = 1; 198 | // We're disabling SurpriseRemovalOK in order to 199 | // receive device removal PnP events. 200 | DeviceCapabilitiesEx->SurpriseRemovalOK = 0; 201 | DeviceCapabilitiesEx->Removable = 1; 202 | DeviceCapabilitiesEx->EjectSupported = 1; 203 | 204 | SrbStatus = SRB_STATUS_SUCCESS; 205 | } 206 | break; 207 | 208 | default: 209 | WNBD_LOG_INFO("Untreated SCSI request. PnP action: %x, " 210 | "PnP flag: %x", PnPAction, SrbPnPFlags); 211 | break; 212 | } 213 | 214 | WNBD_LOG_DEBUG("Exit with SrbStatus: %s", 215 | WnbdToStringSrbStatus(SrbStatus)); 216 | 217 | return SrbStatus; 218 | } 219 | -------------------------------------------------------------------------------- /driver/scsi_function.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef SCSI_FUNCTION_H 8 | #define SCSI_FUNCTION_H 1 9 | 10 | #include "common.h" 11 | 12 | UCHAR 13 | WnbdAbortFunction(_In_ PVOID DeviceExtension, 14 | _In_ PVOID Srb); 15 | 16 | UCHAR 17 | WnbdResetLogicalUnitFunction(_In_ PVOID DeviceExtension, 18 | _In_ PVOID Srb); 19 | 20 | UCHAR 21 | WnbdResetDeviceFunction(_In_ PVOID DeviceExtension, 22 | _In_ PVOID Srb); 23 | 24 | UCHAR 25 | WnbdExecuteScsiFunction(_In_ PVOID DeviceExtension, 26 | _In_ PVOID Srb, 27 | _Inout_ PBOOLEAN Complete); 28 | 29 | UCHAR 30 | WnbdPNPFunction(_In_ PVOID Srb); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /driver/scsi_operation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef SCSI_OPERATION_H 8 | #define SCSI_OPERATION_H 1 9 | 10 | #include "common.h" 11 | #include "userspace.h" 12 | 13 | NTSTATUS 14 | WnbdHandleSrbOperation(_In_ PWNBD_EXTENSION DeviceExtension, 15 | _In_ PWNBD_DISK_DEVICE ScsiDeviceExtension, 16 | _In_ PVOID Srb); 17 | #endif 18 | -------------------------------------------------------------------------------- /driver/scsi_trace.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "common.h" 8 | #include "scsi_trace.h" 9 | 10 | #define CASE_STR(x) case x: return #x; 11 | 12 | _Use_decl_annotations_ 13 | PCHAR 14 | WnbdToStringSrbFunction(ULONG SrbFunction) 15 | { 16 | switch(SrbFunction) { 17 | CASE_STR(SRB_FUNCTION_EXECUTE_SCSI) 18 | CASE_STR(SRB_FUNCTION_CLAIM_DEVICE) 19 | CASE_STR(SRB_FUNCTION_IO_CONTROL) 20 | CASE_STR(SRB_FUNCTION_RECEIVE_EVENT) 21 | CASE_STR(SRB_FUNCTION_RELEASE_QUEUE) 22 | CASE_STR(SRB_FUNCTION_ATTACH_DEVICE) 23 | CASE_STR(SRB_FUNCTION_RELEASE_DEVICE) 24 | CASE_STR(SRB_FUNCTION_SHUTDOWN) 25 | CASE_STR(SRB_FUNCTION_FLUSH) 26 | CASE_STR(SRB_FUNCTION_PROTOCOL_COMMAND) 27 | CASE_STR(SRB_FUNCTION_ABORT_COMMAND) 28 | CASE_STR(SRB_FUNCTION_RELEASE_RECOVERY) 29 | CASE_STR(SRB_FUNCTION_RESET_BUS) 30 | CASE_STR(SRB_FUNCTION_RESET_DEVICE) 31 | CASE_STR(SRB_FUNCTION_TERMINATE_IO) 32 | CASE_STR(SRB_FUNCTION_FLUSH_QUEUE) 33 | CASE_STR(SRB_FUNCTION_REMOVE_DEVICE) 34 | CASE_STR(SRB_FUNCTION_WMI) 35 | CASE_STR(SRB_FUNCTION_LOCK_QUEUE) 36 | CASE_STR(SRB_FUNCTION_UNLOCK_QUEUE) 37 | CASE_STR(SRB_FUNCTION_QUIESCE_DEVICE) 38 | CASE_STR(SRB_FUNCTION_RESET_LOGICAL_UNIT) 39 | CASE_STR(SRB_FUNCTION_SET_LINK_TIMEOUT) 40 | CASE_STR(SRB_FUNCTION_LINK_TIMEOUT_OCCURRED) 41 | CASE_STR(SRB_FUNCTION_LINK_TIMEOUT_COMPLETE) 42 | CASE_STR(SRB_FUNCTION_POWER) 43 | CASE_STR(SRB_FUNCTION_PNP) 44 | CASE_STR(SRB_FUNCTION_DUMP_POINTERS) 45 | CASE_STR(SRB_FUNCTION_FREE_DUMP_POINTERS) 46 | default: 47 | return "UNKNOWN_SRB_FUNCTION"; 48 | } 49 | } 50 | 51 | _Use_decl_annotations_ 52 | PCHAR 53 | WnbdToStringSrbStatus(UCHAR SrbStatus) 54 | { 55 | switch(SrbStatus) { 56 | CASE_STR(SRB_STATUS_PENDING) 57 | CASE_STR(SRB_STATUS_SUCCESS) 58 | CASE_STR(SRB_STATUS_ABORTED) 59 | CASE_STR(SRB_STATUS_ABORT_FAILED) 60 | CASE_STR(SRB_STATUS_ERROR) 61 | CASE_STR(SRB_STATUS_BUSY) 62 | CASE_STR(SRB_STATUS_INVALID_REQUEST) 63 | CASE_STR(SRB_STATUS_INVALID_PATH_ID) 64 | CASE_STR(SRB_STATUS_NO_DEVICE) 65 | CASE_STR(SRB_STATUS_TIMEOUT) 66 | CASE_STR(SRB_STATUS_SELECTION_TIMEOUT) 67 | CASE_STR(SRB_STATUS_COMMAND_TIMEOUT) 68 | CASE_STR(SRB_STATUS_MESSAGE_REJECTED) 69 | CASE_STR(SRB_STATUS_BUS_RESET) 70 | CASE_STR(SRB_STATUS_PARITY_ERROR) 71 | CASE_STR(SRB_STATUS_REQUEST_SENSE_FAILED) 72 | CASE_STR(SRB_STATUS_NO_HBA) 73 | CASE_STR(SRB_STATUS_DATA_OVERRUN) 74 | CASE_STR(SRB_STATUS_UNEXPECTED_BUS_FREE) 75 | CASE_STR(SRB_STATUS_PHASE_SEQUENCE_FAILURE) 76 | CASE_STR(SRB_STATUS_BAD_SRB_BLOCK_LENGTH) 77 | CASE_STR(SRB_STATUS_REQUEST_FLUSHED) 78 | CASE_STR(SRB_STATUS_INVALID_LUN) 79 | CASE_STR(SRB_STATUS_INVALID_TARGET_ID) 80 | CASE_STR(SRB_STATUS_BAD_FUNCTION) 81 | CASE_STR(SRB_STATUS_ERROR_RECOVERY) 82 | CASE_STR(SRB_STATUS_NOT_POWERED) 83 | CASE_STR(SRB_STATUS_LINK_DOWN) 84 | // Recent SDK additions 85 | #ifdef SRB_STATUS_INSUFFICIENT_RESOURCES 86 | CASE_STR(SRB_STATUS_INSUFFICIENT_RESOURCES) 87 | #endif 88 | #ifdef SRB_STATUS_THROTTLED_REQUEST 89 | CASE_STR(SRB_STATUS_THROTTLED_REQUEST) 90 | #endif 91 | default: 92 | return "UNKNOWN_SRB_STATUS"; 93 | } 94 | } 95 | 96 | _Use_decl_annotations_ 97 | PCHAR 98 | WnbdToStringPnpMinorFunction(UCHAR PnpMinorFunction) 99 | { 100 | switch (PnpMinorFunction) { 101 | CASE_STR(IRP_MN_START_DEVICE) 102 | CASE_STR(IRP_MN_QUERY_REMOVE_DEVICE) 103 | CASE_STR(IRP_MN_REMOVE_DEVICE) 104 | CASE_STR(IRP_MN_CANCEL_REMOVE_DEVICE) 105 | CASE_STR(IRP_MN_STOP_DEVICE) 106 | CASE_STR(IRP_MN_QUERY_STOP_DEVICE) 107 | CASE_STR(IRP_MN_CANCEL_STOP_DEVICE) 108 | CASE_STR(IRP_MN_QUERY_DEVICE_RELATIONS) 109 | CASE_STR(IRP_MN_QUERY_INTERFACE) 110 | CASE_STR(IRP_MN_QUERY_CAPABILITIES) 111 | CASE_STR(IRP_MN_QUERY_RESOURCES) 112 | CASE_STR(IRP_MN_QUERY_RESOURCE_REQUIREMENTS) 113 | CASE_STR(IRP_MN_QUERY_DEVICE_TEXT) 114 | CASE_STR(IRP_MN_FILTER_RESOURCE_REQUIREMENTS) 115 | CASE_STR(IRP_MN_READ_CONFIG) 116 | CASE_STR(IRP_MN_WRITE_CONFIG) 117 | CASE_STR(IRP_MN_EJECT) 118 | CASE_STR(IRP_MN_SET_LOCK) 119 | CASE_STR(IRP_MN_QUERY_ID) 120 | CASE_STR(IRP_MN_QUERY_PNP_DEVICE_STATE) 121 | CASE_STR(IRP_MN_QUERY_BUS_INFORMATION) 122 | CASE_STR(IRP_MN_DEVICE_USAGE_NOTIFICATION) 123 | CASE_STR(IRP_MN_SURPRISE_REMOVAL) 124 | CASE_STR(IRP_MN_DEVICE_ENUMERATED) 125 | default: 126 | return "IRP_MN_UNKNOWN"; 127 | } 128 | } 129 | 130 | _Use_decl_annotations_ 131 | PCHAR 132 | WnbdToStringScsiAdapterCtrlType(UCHAR ControlType) 133 | { 134 | switch (ControlType){ 135 | CASE_STR(ScsiQuerySupportedControlTypes) 136 | CASE_STR(ScsiStopAdapter) 137 | CASE_STR(ScsiRestartAdapter) 138 | CASE_STR(ScsiSetBootConfig) 139 | CASE_STR(ScsiSetRunningConfig) 140 | CASE_STR(ScsiAdapterControlMax) 141 | default: 142 | return "Unknown"; 143 | } 144 | } -------------------------------------------------------------------------------- /driver/scsi_trace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef SCSI_TRACE_H 8 | #define SCSI_TRACE_H 1 9 | 10 | #include "common.h" 11 | 12 | PCHAR 13 | WnbdToStringSrbFunction(_In_ ULONG SrbFunction); 14 | 15 | PCHAR 16 | WnbdToStringSrbStatus(_In_ UCHAR SrbStatus); 17 | 18 | PCHAR 19 | WnbdToStringPnpMinorFunction(_In_ UCHAR PnpMinorFunction); 20 | 21 | PCHAR 22 | WnbdToStringScsiAdapterCtrlType(_In_ UCHAR ControlType); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /driver/srb_helper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef SRB_HELPER_H 8 | #define SRB_HELPER_H 1 9 | 10 | #include 11 | #include 12 | 13 | FORCEINLINE VOID 14 | SrbCdbGetRange( 15 | _In_ PCDB Cdb, 16 | _In_ PUINT64 POffset, 17 | _In_ PUINT32 PLength, 18 | _In_ PUINT32 PForceUnitAccess) 19 | { 20 | ASSERT( 21 | SCSIOP_READ6 == Cdb->AsByte[0] || 22 | SCSIOP_READ == Cdb->AsByte[0] || 23 | SCSIOP_READ12 == Cdb->AsByte[0] || 24 | SCSIOP_READ16 == Cdb->AsByte[0] || 25 | SCSIOP_WRITE6 == Cdb->AsByte[0] || 26 | SCSIOP_WRITE == Cdb->AsByte[0] || 27 | SCSIOP_WRITE12 == Cdb->AsByte[0] || 28 | SCSIOP_WRITE16 == Cdb->AsByte[0] || 29 | SCSIOP_SYNCHRONIZE_CACHE == Cdb->AsByte[0] || 30 | SCSIOP_SYNCHRONIZE_CACHE16 == Cdb->AsByte[0]); 31 | 32 | switch (Cdb->AsByte[0] & 0xE0) 33 | { 34 | case 0 << 5: 35 | /* CDB6 */ 36 | if (0 != POffset) 37 | *POffset = 38 | ((UINT64)Cdb->CDB6READWRITE.LogicalBlockMsb1 << 16) | 39 | ((UINT64)Cdb->CDB6READWRITE.LogicalBlockMsb0 << 8) | 40 | ((UINT64)Cdb->CDB6READWRITE.LogicalBlockLsb); 41 | if (0 != PLength) 42 | *PLength = 0 != Cdb->CDB6READWRITE.TransferBlocks ? 43 | ((UINT32)Cdb->CDB6READWRITE.TransferBlocks) : 44 | 256; 45 | if (0 != PForceUnitAccess) 46 | *PForceUnitAccess = 0; 47 | break; 48 | 49 | case 1 << 5: 50 | case 2 << 5: 51 | /* CDB10 */ 52 | if (0 != POffset) 53 | *POffset = 54 | ((UINT64)Cdb->CDB10.LogicalBlockByte0 << 24) | 55 | ((UINT64)Cdb->CDB10.LogicalBlockByte1 << 16) | 56 | ((UINT64)Cdb->CDB10.LogicalBlockByte2 << 8) | 57 | ((UINT64)Cdb->CDB10.LogicalBlockByte3); 58 | if (0 != PLength) 59 | *PLength = 60 | ((UINT32)Cdb->CDB10.TransferBlocksMsb << 8) | 61 | ((UINT32)Cdb->CDB10.TransferBlocksLsb); 62 | if (0 != PForceUnitAccess) 63 | *PForceUnitAccess = Cdb->CDB10.ForceUnitAccess; 64 | break; 65 | 66 | case 4 << 5: 67 | /* CDB16 */ 68 | if (0 != POffset) 69 | *POffset = 70 | ((UINT64)Cdb->CDB16.LogicalBlock[0] << 56) | 71 | ((UINT64)Cdb->CDB16.LogicalBlock[1] << 48) | 72 | ((UINT64)Cdb->CDB16.LogicalBlock[2] << 40) | 73 | ((UINT64)Cdb->CDB16.LogicalBlock[3] << 32) | 74 | ((UINT64)Cdb->CDB16.LogicalBlock[4] << 24) | 75 | ((UINT64)Cdb->CDB16.LogicalBlock[5] << 16) | 76 | ((UINT64)Cdb->CDB16.LogicalBlock[6] << 8) | 77 | ((UINT64)Cdb->CDB16.LogicalBlock[7]); 78 | if (0 != PLength) 79 | *PLength = 80 | ((UINT32)Cdb->CDB16.TransferLength[0] << 24) | 81 | ((UINT32)Cdb->CDB16.TransferLength[1] << 16) | 82 | ((UINT32)Cdb->CDB16.TransferLength[2] << 8) | 83 | ((UINT32)Cdb->CDB16.TransferLength[3]); 84 | if (0 != PForceUnitAccess) 85 | *PForceUnitAccess = Cdb->CDB16.ForceUnitAccess; 86 | break; 87 | 88 | case 5 << 5: 89 | /* CDB12 */ 90 | if (0 != POffset) 91 | *POffset = 92 | ((UINT64)Cdb->CDB12.LogicalBlock[0] << 24) | 93 | ((UINT64)Cdb->CDB12.LogicalBlock[1] << 16) | 94 | ((UINT64)Cdb->CDB12.LogicalBlock[2] << 8) | 95 | ((UINT64)Cdb->CDB12.LogicalBlock[3]); 96 | if (0 != PLength) 97 | *PLength = 98 | ((UINT32)Cdb->CDB12.TransferLength[0] << 24) | 99 | ((UINT32)Cdb->CDB12.TransferLength[1] << 16) | 100 | ((UINT32)Cdb->CDB12.TransferLength[2] << 8) | 101 | ((UINT32)Cdb->CDB12.TransferLength[3]); 102 | if (0 != PForceUnitAccess) 103 | *PForceUnitAccess = Cdb->CDB12.ForceUnitAccess; 104 | break; 105 | } 106 | } 107 | 108 | #define CHECK_MODE_SENSE(Cdb, Page) \ 109 | (MODE_SENSE_CHANGEABLE_VALUES == (Cdb)->MODE_SENSE.Pc || \ 110 | (Page != (Cdb)->MODE_SENSE.PageCode && \ 111 | MODE_SENSE_RETURN_ALL != (Cdb)->MODE_SENSE.PageCode)) 112 | 113 | #define CHECK_MODE_SENSE10(Cdb, Page) \ 114 | (MODE_SENSE_CHANGEABLE_VALUES == (Cdb)->MODE_SENSE10.Pc || \ 115 | (Page != (Cdb)->MODE_SENSE10.PageCode && \ 116 | MODE_SENSE_RETURN_ALL != (Cdb)->MODE_SENSE10.PageCode)) 117 | 118 | #endif // !SRB_HELPER_H 119 | -------------------------------------------------------------------------------- /driver/userspace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef USERSPACE_H 8 | #define USERSPACE_H 1 9 | 10 | #include "driver.h" 11 | #include "wnbd_ioctl.h" 12 | #include "scsi_driver_extensions.h" 13 | 14 | NTSTATUS 15 | WnbdParseUserIOCTL(_In_ PWNBD_EXTENSION DeviceExtension, 16 | _In_ PIRP Irp); 17 | 18 | NTSTATUS 19 | WnbdCreateConnection(_In_ PWNBD_EXTENSION DeviceExtension, 20 | _In_ PWNBD_PROPERTIES Properties, 21 | _In_ PWNBD_CONNECTION_INFO ConnectionInfo); 22 | 23 | NTSTATUS 24 | WnbdEnumerateActiveConnections(_In_ PWNBD_EXTENSION DeviceExtension, 25 | _In_ PIRP Irp); 26 | 27 | NTSTATUS 28 | WnbdDeleteConnection(_In_ PWNBD_EXTENSION DeviceExtension, 29 | _In_ PCHAR InstanceName); 30 | 31 | NTSTATUS 32 | WnbdSetDiskSize(_In_ PWNBD_EXTENSION DeviceExtension, 33 | _In_ WNBD_CONNECTION_ID ConnectionId, 34 | _In_ UINT64 BlockCount); 35 | 36 | VOID 37 | WnbdInitScsiIds(); 38 | 39 | VOID 40 | WnbdDeviceMonitorThread(_In_ PVOID Context); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /driver/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef UTIL_H 8 | #define UTIL_H 1 9 | 10 | #include "common.h" 11 | #include "scsi_driver_extensions.h" 12 | 13 | VOID 14 | DrainDeviceQueue(_In_ PWNBD_DISK_DEVICE Device, 15 | _In_ BOOLEAN SubmittedRequests, 16 | _In_ BOOLEAN CheckStaleConn); 17 | 18 | VOID 19 | CompleteRequest(_In_ PWNBD_DISK_DEVICE Device, 20 | _In_ PSRB_QUEUE_ELEMENT Element, 21 | _In_ BOOLEAN FreeElement); 22 | BOOLEAN 23 | HasPendingAsyncRequests(_In_ PWNBD_DISK_DEVICE Device); 24 | 25 | VOID 26 | WnbdCleanupAllDevices(_In_ PWNBD_EXTENSION DeviceExtension); 27 | 28 | // Increments the device rundown protection reference count, preventing 29 | // it from being cleaned up. 30 | BOOLEAN 31 | WnbdAcquireDevice(_In_ PWNBD_DISK_DEVICE Device); 32 | // Decrements the reference count. All "WnbdAcquireDevice" calls must 33 | // be paired with a "WnbdReleaseDevice" call. 34 | VOID 35 | WnbdReleaseDevice(_In_ PWNBD_DISK_DEVICE Device); 36 | // Signals the device cleanup thread, setting the "*TerminateDevice" flags 37 | // to avoid further processing. 38 | VOID 39 | WnbdDisconnectAsync(PWNBD_DISK_DEVICE Device); 40 | // The specified device must be acquired. It will be released by 41 | // WnbdDisconnectSync. 42 | VOID 43 | WnbdDisconnectSync(_In_ PWNBD_DISK_DEVICE Device); 44 | 45 | // Device returned by WnbdFindDevice* functions must be subsequently 46 | // relased using WnbdReleaseDevice, if "Acquire" is set. 47 | // Unacquired device pointers must not be dereferenced. 48 | PWNBD_DISK_DEVICE 49 | WnbdFindDeviceByAddr( 50 | _In_ PWNBD_EXTENSION DeviceExtension, 51 | _In_ UCHAR PathId, 52 | _In_ UCHAR TargetId, 53 | _In_ UCHAR Lun, 54 | _In_ BOOLEAN Acquire); 55 | PWNBD_DISK_DEVICE 56 | WnbdFindDeviceByConnId( 57 | _In_ PWNBD_EXTENSION DeviceExtension, 58 | _In_ UINT64 ConnectionId, 59 | _In_ BOOLEAN Acquire); 60 | PWNBD_DISK_DEVICE 61 | WnbdFindDeviceByInstanceName( 62 | _In_ PWNBD_EXTENSION DeviceExtension, 63 | _In_ PCHAR InstanceName, 64 | _In_ BOOLEAN Acquire); 65 | 66 | BOOLEAN 67 | IsReadSrb(_In_ PVOID Srb); 68 | BOOLEAN 69 | IsPerResInSrb(_In_ PVOID Srb); 70 | 71 | BOOLEAN ValidateScsiRequest( 72 | _In_ PWNBD_DISK_DEVICE Device, 73 | _In_ PSRB_QUEUE_ELEMENT Element); 74 | 75 | #define LIST_FORALL_SAFE(_headPtr, _itemPtr, _nextPtr) \ 76 | for (_itemPtr = (_headPtr)->Flink, _nextPtr = (_itemPtr)->Flink; \ 77 | _itemPtr != _headPtr; \ 78 | _itemPtr = _nextPtr, _nextPtr = (_itemPtr)->Flink) 79 | 80 | #endif 81 | 82 | void SetSrbStatus( 83 | PVOID Srb, 84 | PWNBD_STATUS Status); 85 | 86 | static inline int 87 | ScsiOpToWnbdReqType(int ScsiOp) 88 | { 89 | switch (ScsiOp) { 90 | case SCSIOP_READ6: 91 | case SCSIOP_READ: 92 | case SCSIOP_READ12: 93 | case SCSIOP_READ16: 94 | return WnbdReqTypeRead; 95 | case SCSIOP_WRITE6: 96 | case SCSIOP_WRITE: 97 | case SCSIOP_WRITE12: 98 | case SCSIOP_WRITE16: 99 | return WnbdReqTypeWrite; 100 | case SCSIOP_UNMAP: 101 | return WnbdReqTypeUnmap; 102 | case SCSIOP_SYNCHRONIZE_CACHE: 103 | case SCSIOP_SYNCHRONIZE_CACHE16: 104 | return WnbdReqTypeFlush; 105 | case SCSIOP_PERSISTENT_RESERVE_IN: 106 | return WnbdReqTypePersistResIn; 107 | case SCSIOP_PERSISTENT_RESERVE_OUT: 108 | return WnbdReqTypePersistResOut; 109 | default: 110 | return WnbdReqTypeUnknown; 111 | } 112 | } 113 | 114 | VOID WnbdSendIoctl( 115 | ULONG ControlCode, 116 | PDEVICE_OBJECT DeviceObject, 117 | PVOID InputBuffer, 118 | ULONG InputBufferLength, 119 | PVOID OutputBuffer, 120 | ULONG OutputBufferLength, 121 | PIO_STATUS_BLOCK IoStatus); 122 | NTSTATUS 123 | WnbdGetScsiAddress( 124 | PDEVICE_OBJECT DeviceObject, 125 | PSCSI_ADDRESS ScsiAddress); 126 | NTSTATUS 127 | WnbdGetDiskNumber( 128 | PDEVICE_OBJECT DeviceObject, 129 | PULONG DiskNumber); 130 | NTSTATUS 131 | WnbdGetDiskInstancePath( 132 | PDEVICE_OBJECT DeviceObject, 133 | PWSTR Buffer, 134 | DWORD BufferSize, 135 | PULONG RequiredBufferSize); 136 | 137 | static inline NTSTATUS WstrToBool(const PWCHAR string, PBOOLEAN Value) { 138 | if (!_wcsicmp(string, L"1") || 139 | !_wcsicmp(string, L"t") || 140 | !_wcsicmp(string, L"true") || 141 | !_wcsicmp(string, L"yes") || 142 | !_wcsicmp(string, L"y")) 143 | { 144 | *Value = TRUE; 145 | return STATUS_SUCCESS; 146 | } 147 | if (!_wcsicmp(string, L"0") || 148 | !_wcsicmp(string, L"f") || 149 | !_wcsicmp(string, L"false") || 150 | !_wcsicmp(string, L"no") || 151 | !_wcsicmp(string, L"n")) 152 | { 153 | *Value = FALSE; 154 | return STATUS_SUCCESS; 155 | } 156 | return STATUS_INVALID_PARAMETER; 157 | } 158 | -------------------------------------------------------------------------------- /driver/wnbd.inf: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2019 SUSE LLC 3 | ; 4 | ; Licensed under LGPL-2.1 (see LICENSE) 5 | ; 6 | 7 | [Version] 8 | Signature="$WINDOWS NT$" 9 | Class=SCSIAdapter 10 | ClassGUID={4D36E97B-E325-11CE-BFC1-08002BE10318} 11 | Provider=%wnbd% 12 | ; The driver version is updated automatically using the git tag 13 | DriverVer = 02/17/2020,2.24.28.428 14 | CatalogFile = wnbd.cat 15 | PnpLockdown = 1 16 | 17 | [DestinationDirs] 18 | DefaultDestDir = 12 19 | wnbdSVM.ntamd64.Application = 11 20 | 21 | [Manufacturer] 22 | %wnbd%=wnbdSVM, NTamd64.10.0...17763 23 | 24 | [wnbdSVM.NTamd64.10.0...17763] 25 | %WNBDVMDeviceDesc%=wnbdSVM_Device, %rootstr% 26 | 27 | [wnbdSVM_Device] 28 | CopyFiles=@wnbd.sys 29 | 30 | [wnbdSVM_Device.HW] 31 | AddReg = wnbdSVM_Device_AddReg 32 | 33 | [wnbdSVM_Device_AddReg] 34 | HKR, "ScsiPort", "NeedsSystemShutdownNotification", 0x00010001, 1 35 | 36 | [wnbdSVM_Device.Services] 37 | AddService = wnbd, %SPSVCINST_ASSOCSERVICE%, wnbdSVM_Service_Inst 38 | 39 | [SourceDisksNames.amd64] 40 | 0 = %DiskId1%,,, 41 | 42 | [SourceDisksFiles.amd64] 43 | wnbd.sys = 0 44 | 45 | [wnbdSVM_Service_Inst] 46 | DisplayName = %WNBDVMDeviceDesc% 47 | ServiceType = %SERVICE_KERNEL_DRIVER% 48 | StartType = %SERVICE_BOOT_START% 49 | ErrorControl = %SERVICE_ERROR_NORMAL% 50 | ServiceBinary = %12%\wnbd.sys 51 | LoadOrderGroup = SCSI Miniport 52 | AddReg = pnpsafe_isa_addreg 53 | 54 | [pnpsafe_isa_addreg] 55 | HKR, "Parameters", "BusType", %REG_DWORD%, 0x0000000A 56 | 57 | [Strings] 58 | wnbd = "SUSE LLC" 59 | SCSIClassName = "SCSI and RAID controllers" 60 | WNBDVMDeviceDesc = "WNBD SCSI Virtual Adapter" 61 | DiskId1 = "WNBD SCSI Virtual Adapter Device Installation Disk #1" 62 | rootstr = "root\wnbd" 63 | 64 | SPSVCINST_ASSOCSERVICE = 0x00000002 65 | SERVICE_KERNEL_DRIVER = 1 66 | SERVICE_BOOT_START = 0 67 | SERVICE_ERROR_NORMAL = 1 68 | 69 | REG_DWORD = 0x00010001 70 | REG_BINARY = 0x00000001 71 | REG_SZ = 0x00000000 72 | -------------------------------------------------------------------------------- /driver/wnbd.rc: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: LGPL-2.1-only 3 | * 4 | * Copyright (C) 2019-2020 SUSE LLC 5 | * 6 | * This library is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published 8 | * by the Free Software Foundation; version 2.1. 9 | * 10 | * This library is distributed in the hope that it will be useful, but WITHOUT 11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library; if not, write to the 17 | * 18 | * Free Software Foundation, Inc., 19 | * 51 Franklin Street, Fifth Floor, 20 | * Boston, MA 02110-1301 USA 21 | * 22 | */ 23 | 24 | #include 25 | #include 26 | #include "..\include\version.h" 27 | #include "..\include\vendor.h" 28 | 29 | #define VER_INTERNALNAME_STR "wnbd.sys" 30 | #define VER_ORIGINALFILENAME_STR "wnbd.sys" 31 | 32 | #undef VER_PRODUCTVERSION 33 | #define VER_PRODUCTVERSION WNBD_VERSION_MAJOR,WNBD_VERSION_MINOR,WNBD_VERSION_PATCH,WNBD_COMMIT_COUNT 34 | 35 | #undef VER_PRODUCTVERSION_STR 36 | #define VER_PRODUCTVERSION_STR WNBD_VERSION_STR 37 | 38 | #define VER_FILEVERSION WNBD_VERSION_MAJOR,WNBD_VERSION_MINOR,WNBD_VERSION_PATCH,WNBD_COMMIT_COUNT 39 | #define VER_FILEVERSION_STR WNBD_VERSION_STR_MS 40 | 41 | #undef VER_COMPANYNAME_STR 42 | #define VER_COMPANYNAME_STR WNBD_COMPANY_STR 43 | 44 | #undef VER_FILEDESCRIPTION_STR 45 | #undef VER_PRODUCTNAME_STR 46 | #define VER_FILEDESCRIPTION_STR WNBD_FILEDESCRIPTION_STR 47 | #define VER_PRODUCTNAME_STR WNBD_PRODUCTNAME_STR 48 | 49 | #define VER_FILESUBTYPE VFT2_DRV_SYSTEM 50 | 51 | #define VER_FILETYPE VFT_DRV 52 | 53 | #define VER_LEGALCOPYRIGHT_STR "Copyright \251 2019-2020 SUSE LLC All rights reserved.", "\0" 54 | 55 | /* Strings to be translated */ 56 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 57 | #pragma code_page(1252) 58 | 59 | /* The following are used to generate ETW events inside the binaries */ 60 | 1 11 "events_MSG00001.bin" 61 | 1 WEVT_TEMPLATE "eventsTEMP.BIN" 62 | 63 | #include "common.ver" 64 | -------------------------------------------------------------------------------- /driver/wnbd_dispatch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #ifndef WNBD_DISPATCH_H 8 | #define WNBD_DISPATCH_H 1 9 | 10 | #include "common.h" 11 | #include "userspace.h" 12 | 13 | // TODO: consider moving this to util.h 14 | NTSTATUS LockUsermodeBuffer( 15 | PVOID Buffer, UINT32 BufferSize, BOOLEAN Writeable, 16 | PVOID* OutBuffer, PMDL* OutMdl, BOOLEAN* Locked); 17 | 18 | NTSTATUS WnbdDispatchRequest( 19 | PIRP Irp, 20 | PWNBD_DISK_DEVICE Device, 21 | PWNBD_IOCTL_FETCH_REQ_COMMAND Command); 22 | 23 | NTSTATUS WnbdHandleResponse( 24 | PIRP Irp, 25 | PWNBD_DISK_DEVICE Device, 26 | PWNBD_IOCTL_SEND_RSP_COMMAND Command); 27 | 28 | #endif // WNBD_DISPATCH_H 29 | -------------------------------------------------------------------------------- /get_dependencies.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [switch]$Clean 3 | ) 4 | 5 | $scriptLocation = [System.IO.Path]::GetDirectoryName( 6 | $myInvocation.MyCommand.Definition) 7 | $ErrorActionPreference = "Stop" 8 | 9 | # Enforce Tls1.2, as most modern websites require it 10 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 11 | 12 | $nugetUrl = "https://dist.nuget.org/win-x86-commandline/v4.5.1/nuget.exe" 13 | 14 | $depsDir = "$scriptLocation\vstudio\deps" 15 | $nugetPath = "$depsDir\nuget.exe" 16 | $depsConfig = "$scriptLocation\packages.config" 17 | $completeFlag = "$depsDir\complete" 18 | 19 | function safe_exec($cmd) { 20 | cmd /c "$cmd 2>&1" 21 | $exitCode = $LASTEXITCODE 22 | if ($exitCode) { 23 | throw "Command failed: $cmd. Exit code: $exitCode" 24 | } 25 | } 26 | 27 | if ($Clean -and (test-path $depsDir)) { 28 | rm -recurse -force $depsDir 29 | Write-Host "Cleaning up dependencies: $depsDir" 30 | } 31 | 32 | mkdir -force $depsDir 33 | if (!(test-path $nugetPath)) { 34 | Write-Host "Fetching nuget from $nugetUrl." 35 | Invoke-WebRequest $nugetUrl -OutFile $nugetPath 36 | } 37 | 38 | if (!(test-path $completeFlag)) { 39 | Write-Host "Retrieving dependencies." 40 | safe_exec "$nugetPath install $depsConfig -OutputDirectory `"$depsDir`"" 41 | sc $completeFlag "Finished retrieving dependencies." 42 | } 43 | else { 44 | write-host "Nuget dependencies already fetched." 45 | } 46 | 47 | -------------------------------------------------------------------------------- /include/vendor.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * The variables in this vendor.h file can be used to provide vendor specific 3 | * strings to the properities of the WNBD driver. Replace the contents of 4 | * this file as necessary. 5 | */ 6 | 7 | #define WNBD_COMPANY_STR "WNBD" 8 | #define WNBD_FILEDESCRIPTION_STR "WNBD" 9 | #define WNBD_PRODUCTNAME_STR "WNBD" 10 | 11 | -------------------------------------------------------------------------------- /libwnbd/libwnbd.def: -------------------------------------------------------------------------------- 1 | LIBRARY libwnbd 2 | 3 | EXPORTS 4 | WnbdCreate 5 | WnbdRunNbdDaemon 6 | WnbdRemove 7 | WnbdRemoveEx 8 | WnbdSetDiskSize 9 | WnbdClose 10 | WnbdPollDiskNumber 11 | WnbdList 12 | WnbdShow 13 | WnbdGetUserspaceStats 14 | WnbdGetUserContext 15 | WnbdGetDriverStats 16 | WnbdSetLogger 17 | WnbdSetLogLevel 18 | WnbdSetSenseEx 19 | WnbdSetSense 20 | WnbdStartDispatcher 21 | WnbdStopDispatcher 22 | WnbdWaitDispatcher 23 | WnbdSendResponse 24 | WnbdSendResponseEx 25 | WnbdGetConnectionInfo 26 | WnbdGetDriverVersion 27 | WnbdGetLibVersion 28 | WnbdGetDrvOpt 29 | WnbdSetDrvOpt 30 | WnbdResetDrvOpt 31 | WnbdListDrvOpt 32 | WnbdInstallDriver 33 | WnbdUninstallDriver 34 | WnbdOpenAdapter 35 | WnbdResetAdapter 36 | WnbdResetAdapterEx 37 | WnbdGetAdapterDevInst 38 | WnbdRemoveAllDisks 39 | 40 | WnbdIoctlPing 41 | WnbdIoctlCreate 42 | WnbdIoctlRemove 43 | WnbdIoctlList 44 | WnbdIoctlShow 45 | WnbdIoctlStats 46 | WnbdIoctlReloadConfig 47 | WnbdIoctlFetchRequest 48 | WnbdIoctlSetDiskSize 49 | WnbdIoctlSendResponse 50 | WnbdIoctlGetDrvOpt 51 | WnbdIoctlSetDrvOpt 52 | WnbdIoctlResetDrvOpt 53 | WnbdIoctlListDrvOpt 54 | 55 | WnbdIoctlGetIOLimits 56 | -------------------------------------------------------------------------------- /libwnbd/libwnbd.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Analyze 6 | x64 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | x64 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 16.0 35 | {15FFEE2F-F65A-47D1-8389-15E5ADD971CA} 36 | Win32Proj 37 | wnbddll 38 | 10.0 39 | libwnbd 40 | 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v142 52 | true 53 | Unicode 54 | 55 | 56 | DynamicLibrary 57 | false 58 | v142 59 | true 60 | Unicode 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | true 79 | 80 | 81 | false 82 | 83 | 84 | false 85 | true 86 | 87 | 88 | 89 | NotUsing 90 | Level3 91 | true 92 | _DEBUG;WNBDDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 93 | true 94 | pch.h 95 | ..\driver\;..\include;%(AdditionalIncludeDirectories) 96 | stdcpp20 97 | 98 | 99 | Windows 100 | DebugFull 101 | false 102 | libwnbd.def 103 | $(OutDir)\pdb\libwnbd\$(TargetName).pdb 104 | kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) 105 | 106 | 107 | 108 | 109 | NotUsing 110 | Level3 111 | true 112 | true 113 | true 114 | NDEBUG;WNBDDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 115 | true 116 | pch.h 117 | ..\driver\;..\include;%(AdditionalIncludeDirectories) 118 | stdcpp20 119 | 120 | 121 | Windows 122 | true 123 | true 124 | true 125 | false 126 | libwnbd.def 127 | $(OutDir)\pdb\libwnbd\$(TargetName).pdb 128 | kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) 129 | 130 | 131 | 132 | 133 | NotUsing 134 | Level3 135 | true 136 | true 137 | true 138 | NDEBUG;WNBDDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 139 | true 140 | pch.h 141 | ..\driver\;..\include;%(AdditionalIncludeDirectories) 142 | stdcpp20 143 | 144 | 145 | Windows 146 | true 147 | true 148 | true 149 | false 150 | libwnbd.def 151 | $(OutDir)\pdb\libwnbd\$(TargetName).pdb 152 | kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | $(SolutionDir)\deps;$(CAExcludePath) 163 | 164 | -------------------------------------------------------------------------------- /libwnbd/nbd_daemon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "nbd_protocol.h" 19 | #include "wnbd_log.h" 20 | 21 | // Minimal information to identify pending requests. 22 | struct PendingRequestInfo 23 | { 24 | UINT64 RequestHandle; 25 | WnbdRequestType RequestType; 26 | UINT32 Length; 27 | }; 28 | 29 | class NbdDaemon 30 | { 31 | private: 32 | WNBD_PROPERTIES WnbdProps = {0}; 33 | 34 | SOCKET Socket = INVALID_SOCKET; 35 | 36 | std::mutex ShutdownLock; 37 | bool Terminated = false; 38 | bool TerminateInProgress = false; 39 | PWNBD_DISK WnbdDisk = nullptr; 40 | 41 | void* PreallocatedWBuff = nullptr; 42 | ULONG PreallocatedWBuffSz = 0; 43 | void* PreallocatedRBuff = nullptr; 44 | ULONG PreallocatedRBuffSz = 0; 45 | 46 | // NBD replies provide limited information. We need to track 47 | // the request size on our own in order to know how large is 48 | // the data buffer that follows the reply header. 49 | std::unordered_map PendingRequests; 50 | std::mutex PendingRequestsLock; 51 | 52 | std::thread ReplyDispatcher; 53 | 54 | public: 55 | NbdDaemon(PWNBD_PROPERTIES Properties) 56 | { 57 | WnbdProps = *Properties; 58 | } 59 | 60 | ~NbdDaemon() 61 | { 62 | Shutdown(); 63 | Wait(); 64 | 65 | if (ReplyDispatcher.joinable()) { 66 | LogInfo("Waiting for NBD reply dispatcher thread."); 67 | ReplyDispatcher.join(); 68 | LogInfo("NBD reply dispatcher stopped."); 69 | } 70 | 71 | if (PreallocatedRBuff) { 72 | free(PreallocatedRBuff); 73 | } 74 | if (PreallocatedWBuff) { 75 | free(PreallocatedWBuff); 76 | } 77 | 78 | if (WnbdDisk) { 79 | WnbdClose(WnbdDisk); 80 | } 81 | } 82 | 83 | DWORD Start(); 84 | DWORD Wait(); 85 | DWORD Shutdown(bool HardRemove=false); 86 | 87 | private: 88 | DWORD TryStart(); 89 | DWORD ConnectNbdServer( 90 | std::string HostName, 91 | uint32_t PortNumber); 92 | DWORD DisconnectNbd(); 93 | 94 | void NbdReplyWorker(); 95 | DWORD ProcessNbdReply(LPOVERLAPPED Overlapped); 96 | 97 | // WNBD IO entry points 98 | static void Read( 99 | PWNBD_DISK Disk, 100 | UINT64 RequestHandle, 101 | PVOID Buffer, 102 | UINT64 BlockAddress, 103 | UINT32 BlockCount, 104 | BOOLEAN ForceUnitAccess); 105 | static void Write( 106 | PWNBD_DISK Disk, 107 | UINT64 RequestHandle, 108 | PVOID Buffer, 109 | UINT64 BlockAddress, 110 | UINT32 BlockCount, 111 | BOOLEAN ForceUnitAccess); 112 | static void Flush( 113 | PWNBD_DISK Disk, 114 | UINT64 RequestHandle, 115 | UINT64 BlockAddress, 116 | UINT32 BlockCount); 117 | static void Unmap( 118 | PWNBD_DISK Disk, 119 | UINT64 RequestHandle, 120 | PWNBD_UNMAP_DESCRIPTOR Descriptors, 121 | UINT32 Count); 122 | 123 | static constexpr WNBD_INTERFACE WnbdInterface = 124 | { 125 | Read, 126 | Write, 127 | Flush, 128 | Unmap, 129 | }; 130 | }; -------------------------------------------------------------------------------- /libwnbd/nbd_protocol.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * Copyright (c) 2023 Cloudbase Solutions 4 | * 5 | * Licensed under LGPL-2.1 (see LICENSE) 6 | */ 7 | 8 | #pragma once 9 | 10 | #pragma warning(push) 11 | #pragma warning(disable:26812) 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #define NBD_REQUEST_MAGIC 0x25609513 19 | #define NBD_REPLY_MAGIC 0x67446698 20 | 21 | /* values for flags field, these are server interaction specific. */ 22 | #define NBD_FLAG_HAS_FLAGS (1 << 0) /* nbd-server supports flags */ 23 | #define NBD_FLAG_READ_ONLY (1 << 1) /* device is read-only */ 24 | #define NBD_FLAG_SEND_FLUSH (1 << 2) /* can flush writeback cache */ 25 | #define NBD_FLAG_SEND_FUA (1 << 3) /* send FUA (forced unit access) */ 26 | /* there is a gap here to match userspace */ 27 | #define NBD_FLAG_SEND_TRIM (1 << 5) /* send trim/discard */ 28 | #define NBD_FLAG_CAN_MULTI_CONN (1 << 8) /* Server supports multiple connections per export. */ 29 | 30 | /* values for cmd flags in the upper 16 bits of request type */ 31 | #define NBD_CMD_FLAG_FUA (1 << 16) /* FUA (forced unit access) op */ 32 | 33 | const UINT64 CLIENT_MAGIC = 0x00420281861253LL; 34 | const UINT64 OPTION_MAGIC = 0x49484156454F5054LL; 35 | const UINT64 REPLY_MAGIC = 0x3e889045565a9LL; 36 | 37 | #define CHECK_NBD_FLAG(nbd_flags, flag) \ 38 | !!(nbd_flags & NBD_FLAG_HAS_FLAGS && nbd_flags & flag) 39 | #define CHECK_NBD_READONLY(nbd_flags) \ 40 | CHECK_NBD_FLAG(nbd_flags, NBD_FLAG_READ_ONLY) 41 | #define CHECK_NBD_SEND_FUA(nbd_flags) \ 42 | CHECK_NBD_FLAG(nbd_flags, NBD_FLAG_SEND_FUA) 43 | #define CHECK_NBD_SEND_TRIM(nbd_flags) \ 44 | CHECK_NBD_FLAG(nbd_flags, NBD_FLAG_SEND_TRIM) 45 | #define CHECK_NBD_SEND_FLUSH(nbd_flags) \ 46 | CHECK_NBD_FLAG(nbd_flags, NBD_FLAG_SEND_FLUSH) 47 | 48 | typedef enum { 49 | NBD_CMD_READ = 0, 50 | NBD_CMD_WRITE = 1, 51 | NBD_CMD_DISC = 2, //DISCONNECT 52 | NBD_CMD_FLUSH = 3, 53 | NBD_CMD_TRIM = 4 54 | } NbdRequestType; 55 | 56 | __pragma(pack(push, 1)) 57 | typedef struct _NBD_REQUEST { 58 | UINT32 Magic; 59 | UINT32 Type; 60 | UINT64 Handle; 61 | UINT64 From; 62 | UINT32 Length; 63 | } NBD_REQUEST, *PNBD_REQUEST; 64 | __pragma(pack(pop)) 65 | 66 | __pragma(pack(push, 1)) 67 | typedef struct _NBD_REPLY { 68 | UINT32 Magic; 69 | UINT32 Error; 70 | UINT64 Handle; 71 | } NBD_REPLY, *PNBD_REPLY; 72 | __pragma(pack(pop)) 73 | 74 | __pragma(pack(push, 1)) 75 | typedef struct _NBD_HANDSHAKE_REQ { 76 | UINT64 Magic; 77 | UINT32 Option; 78 | UINT32 Datasize; 79 | } NBD_HANDSHAKE_REQ, *PNBD_HANDSHAKE_REQ; 80 | __pragma(pack(pop)) 81 | 82 | #pragma warning(disable:4200) 83 | __pragma(pack(push, 1)) 84 | typedef struct _NBD_HANDSHAKE_RPL { 85 | UINT64 Magic; 86 | UINT32 Option; 87 | UINT32 ReplyType; 88 | UINT32 Datasize; 89 | CHAR Data[]; 90 | } NBD_HANDSHAKE_RPL, *PNBD_HANDSHAKE_RPL; 91 | __pragma(pack(pop)) 92 | #pragma warning(default:4200) 93 | 94 | #define NBD_OPT_EXPORT_NAME 1 95 | #define NBD_OPT_GO 7 96 | 97 | #define NBD_REP_ACK 1 98 | #define NBD_REP_INFO 3 99 | #define NBD_REP_FLAG_ERROR 1 << 31 100 | #define NBD_REP_ERR_UNSUP 1 | NBD_REP_FLAG_ERROR 101 | #define NBD_REP_ERR_POLICY 2 | NBD_REP_FLAG_ERROR 102 | 103 | #define NBD_FLAG_FIXED_NEWSTYLE 1 104 | #define NBD_FLAG_NO_ZEROES 2 105 | 106 | #define NBD_INFO_EXPORT 0 107 | 108 | #define INIT_PASSWD "NBDMAGIC" 109 | 110 | #ifdef __cplusplus 111 | extern "C" { 112 | #endif 113 | 114 | DWORD NbdRequest( 115 | _In_ SOCKET Fd, 116 | _In_ UINT64 Offset, 117 | _In_ ULONG Length, 118 | _In_ UINT64 Handle, 119 | _In_ NbdRequestType RequestType); 120 | 121 | DWORD NbdSendWrite( 122 | _In_ SOCKET Fd, 123 | _In_ UINT64 Offset, 124 | _In_ ULONG Length, 125 | _In_ PVOID Data, 126 | _In_ PVOID *PreallocatedBuffer, 127 | _In_ PULONG PreallocatedLength, 128 | _In_ UINT64 Handle, 129 | _In_ UINT32 NbdTransmissionFlags); 130 | 131 | DWORD NbdNegotiate( 132 | _In_ SOCKET Fd, 133 | _In_ PUINT64 Size, 134 | _In_ PUINT16 Flags, 135 | _In_ std::string ExportName, 136 | _In_ UINT32 ClientFlags); 137 | 138 | DWORD NbdReadReply( 139 | _In_ SOCKET Fd, 140 | _Inout_ PNBD_REPLY Reply); 141 | 142 | DWORD RecvExact( 143 | _In_ SOCKET Fd, 144 | _Inout_ PVOID Data, 145 | _In_ size_t Length); 146 | 147 | const char* NbdRequestTypeStr(NbdRequestType RequestType); 148 | 149 | #ifdef __cplusplus 150 | } 151 | #endif 152 | 153 | #pragma warning(pop) 154 | -------------------------------------------------------------------------------- /libwnbd/utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | 18 | #include "wnbd.h" 19 | #include "wnbd_log.h" 20 | 21 | using boost::locale::conv::utf_to_utf; 22 | 23 | 24 | std::wstring to_wstring(const std::string& str) 25 | { 26 | return utf_to_utf(str.c_str(), str.c_str() + str.size()); 27 | } 28 | 29 | std::string to_string(const std::wstring& str) 30 | { 31 | return utf_to_utf(str.c_str(), str.c_str() + str.size()); 32 | } 33 | 34 | std::string win32_strerror(int err) 35 | { 36 | LPSTR msg = NULL; 37 | DWORD msg_len = ::FormatMessageA( 38 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 39 | FORMAT_MESSAGE_FROM_SYSTEM | 40 | FORMAT_MESSAGE_IGNORE_INSERTS, 41 | NULL, 42 | err, 43 | 0, 44 | (LPSTR) &msg, 45 | 0, 46 | NULL); 47 | if (!msg_len) { 48 | std::ostringstream msg_stream; 49 | msg_stream << "Unknown error (" << err << ")."; 50 | return msg_stream.str(); 51 | } 52 | std::string msg_s(msg); 53 | ::LocalFree(msg); 54 | return msg_s; 55 | } 56 | 57 | std::string guid_to_string(GUID guid) 58 | { 59 | char str_guid[40] = { 0 }; 60 | sprintf_s(str_guid, sizeof(str_guid), 61 | "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", 62 | guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], 63 | guid.Data4[1], guid.Data4[2], guid.Data4[3], 64 | guid.Data4[4], guid.Data4[5], guid.Data4[6], 65 | guid.Data4[7]); 66 | return std::string(str_guid); 67 | } 68 | 69 | std::optional<_OSVERSIONINFOEXW> GetWinVersion() 70 | { 71 | HMODULE Lib; 72 | OSVERSIONINFOEXW Version = {}; 73 | Version.dwOSVersionInfoSize = sizeof(Version); 74 | 75 | typedef DWORD(WINAPI* RtlGetVersion_T) (OSVERSIONINFOEXW*); 76 | 77 | Lib = LoadLibraryW(L"Ntdll.dll"); 78 | if (!Lib) { 79 | LogError("Unable to load Ntdll.dll."); 80 | return {}; 81 | } 82 | 83 | auto RtlGetVersionF = (RtlGetVersion_T) GetProcAddress( 84 | Lib, "RtlGetVersion"); 85 | 86 | if (RtlGetVersionF) { 87 | NTSTATUS Status = RtlGetVersionF(&Version); 88 | if (Status) { 89 | LogError("Unable to retrieve Windows version. Error: %d", Status); 90 | } 91 | } else { 92 | LogError("Unable to load RtlGetVersion function."); 93 | } 94 | 95 | FreeLibrary(Lib); 96 | return Version; 97 | } 98 | 99 | bool CheckWindowsVersion(DWORD Major, DWORD Minor, DWORD BuildNumber) 100 | { 101 | auto VersionOpt = GetWinVersion(); 102 | if (!VersionOpt.has_value()) { 103 | LogError("Couldn't retrieve Windows version."); 104 | return false; 105 | } 106 | 107 | auto& Version = VersionOpt.value(); 108 | std::vector VersionVec{ 109 | Version.dwMajorVersion, Version.dwMinorVersion, Version.dwBuildNumber}; 110 | std::vector ExpVersionVec{Major, Minor, BuildNumber}; 111 | 112 | bool VersionSatisfied = !std::lexicographical_compare( 113 | VersionVec.begin(), VersionVec.end(), 114 | ExpVersionVec.begin(), ExpVersionVec.end()); 115 | 116 | LogDebug("Windows version %d.%d.%d %s " 117 | "minimum required version %d.%d.%d.", 118 | Version.dwMajorVersion, Version.dwMinorVersion, 119 | Version.dwBuildNumber, 120 | VersionSatisfied ? "satisfies" : "doesn't satisfy", 121 | Major, Minor, BuildNumber); 122 | 123 | return VersionSatisfied; 124 | } 125 | 126 | bool EnsureWindowsVersionSupported() 127 | { 128 | if (!CheckWindowsVersion( 129 | WNBD_REQ_WIN_VERSION_MAJOR, 130 | WNBD_REQ_WIN_VERSION_MINOR, 131 | WNBD_REQ_WIN_VERSION_BUILD)) { 132 | LogError("Unsupported Windows version. " 133 | "Minimum requirement: %d.%d.%d.", 134 | WNBD_REQ_WIN_VERSION_MAJOR, 135 | WNBD_REQ_WIN_VERSION_MINOR, 136 | WNBD_REQ_WIN_VERSION_BUILD); 137 | return false; 138 | } 139 | return true; 140 | } 141 | -------------------------------------------------------------------------------- /libwnbd/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | std::wstring to_wstring(const std::string& str); 14 | std::string to_string(const std::wstring& str); 15 | std::string win32_strerror(int err); 16 | 17 | std::string guid_to_string(GUID guid); 18 | 19 | std::optional<_OSVERSIONINFOEXW> GetWinVersion(); 20 | bool CheckWindowsVersion(DWORD Major, DWORD Minor, DWORD BuildNumber); 21 | bool EnsureWindowsVersionSupported(); 22 | -------------------------------------------------------------------------------- /libwnbd/wnbd_log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include 8 | 9 | #include "wnbd_log.h" 10 | #include "wnbd.h" 11 | 12 | VOID ConsoleLogger( 13 | WnbdLogLevel LogLevel, 14 | const char* Message, 15 | const char* FileName, 16 | UINT32 Line, 17 | const char* FunctionName) 18 | { 19 | SYSTEMTIME T; 20 | GetLocalTime(&T); 21 | fprintf(stderr, "%02d:%02d:%02d.%03d libwnbd.dll!%s %s %s\n", 22 | T.wHour, T.wMinute, T.wSecond, T.wMilliseconds, 23 | FunctionName, WnbdLogLevelToStr(LogLevel), Message); 24 | } 25 | 26 | static LogMessageFunc WnbdCurrLogger = ConsoleLogger; 27 | static WnbdLogLevel WnbdCurrLogLevel = WnbdLogLevelWarning; 28 | 29 | VOID LogMessage(WnbdLogLevel LogLevel, 30 | const char* FileName, UINT32 Line, const char* FunctionName, 31 | const char* Format, ...) 32 | { 33 | LogMessageFunc CurrLogger = WnbdCurrLogger; 34 | WnbdLogLevel CurrLogLevel = WnbdCurrLogLevel; 35 | 36 | if (!CurrLogger || CurrLogLevel < LogLevel) 37 | return; 38 | 39 | va_list Args; 40 | va_start(Args, Format); 41 | 42 | size_t BufferLength = (size_t)_vscprintf(Format, Args) + 1; 43 | 44 | // TODO: consider enforcing WNBD_LOG_MESSAGE_MAX_SIZE and using a fixed 45 | // size buffer for performance reasons. 46 | char* Buff = (char*) malloc(BufferLength); 47 | if (!Buff) { 48 | va_end(Args); 49 | return; 50 | } 51 | 52 | vsnprintf_s(Buff, BufferLength, BufferLength - 1, Format, Args); 53 | va_end(Args); 54 | 55 | CurrLogger(LogLevel, Buff, FileName, Line, FunctionName); 56 | 57 | free(Buff); 58 | } 59 | 60 | VOID WnbdSetLogger(LogMessageFunc Logger) 61 | { 62 | // Passing NULL should allow completely disabling the logger. 63 | WnbdCurrLogger = Logger; 64 | } 65 | 66 | VOID WnbdSetLogLevel(WnbdLogLevel LogLevel) 67 | { 68 | WnbdCurrLogLevel = LogLevel; 69 | } 70 | -------------------------------------------------------------------------------- /libwnbd/wnbd_log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "wnbd.h" 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | VOID ConsoleLogger( 16 | WnbdLogLevel LogLevel, 17 | const char* Message, 18 | const char* FileName, 19 | UINT32 Line, 20 | const char* FunctionName); 21 | 22 | VOID LogMessage( 23 | WnbdLogLevel LogLevel, 24 | const char* FileName, 25 | UINT32 Line, 26 | const char* FunctionName, 27 | const char* Format, ...); 28 | 29 | #define LogCritical(Format, ...) \ 30 | LogMessage(WnbdLogLevelCritical, \ 31 | __FILE__, __LINE__, __FUNCTION__, Format, __VA_ARGS__) 32 | #define LogError(Format, ...) \ 33 | LogMessage(WnbdLogLevelError, \ 34 | __FILE__, __LINE__, __FUNCTION__, Format, __VA_ARGS__) 35 | #define LogWarning(Format, ...) \ 36 | LogMessage(WnbdLogLevelWarning, \ 37 | __FILE__, __LINE__, __FUNCTION__, Format, __VA_ARGS__) 38 | #define LogInfo(Format, ...) \ 39 | LogMessage(WnbdLogLevelInfo, \ 40 | __FILE__, __LINE__, __FUNCTION__, Format, __VA_ARGS__) 41 | #define LogDebug(Format, ...) \ 42 | LogMessage(WnbdLogLevelDebug, \ 43 | __FILE__, __LINE__, __FUNCTION__, Format, __VA_ARGS__) 44 | #define LogTrace(Format, ...) \ 45 | LogMessage(WnbdLogLevelTrace, \ 46 | __FILE__, __LINE__, __FUNCTION__, Format, __VA_ARGS__) 47 | 48 | #ifdef __cplusplus 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/libwnbd_tests.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | {cb9f16ac-7578-4122-a364-a54aa527c426} 15 | Win32Proj 16 | 10.0 17 | Application 18 | v142 19 | Unicode 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Create 39 | Create 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {15ffee2f-f65a-47d1-8389-15e5add971ca} 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Use 65 | pch.h 66 | Disabled 67 | X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 68 | EnableFastChecks 69 | MultiThreadedDebugDLL 70 | Level3 71 | ..\..\include;$(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories) 72 | stdcpp20 73 | 74 | 75 | true 76 | Console 77 | $(OutDir)\libwnbd.lib;ws2_32.lib;%(AdditionalDependencies) 78 | 79 | 80 | 81 | 82 | Use 83 | pch.h 84 | X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 85 | MultiThreadedDLL 86 | Level3 87 | ProgramDatabase 88 | ..\..\include;$(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories) 89 | stdcpp20 90 | 91 | 92 | true 93 | Console 94 | true 95 | true 96 | $(OutDir)\libwnbd.lib;ws2_32.lib;%(AdditionalDependencies) 97 | 98 | 99 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/mock_wnbd_daemon.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "pch.h" 8 | 9 | #include "mock_wnbd_daemon.h" 10 | #include "utils.h" 11 | 12 | MockWnbdDaemon::~MockWnbdDaemon() 13 | { 14 | if (Started && WnbdDisk) { 15 | Shutdown(); 16 | 17 | WnbdClose(WnbdDisk); 18 | 19 | Started = false; 20 | } 21 | } 22 | 23 | void MockWnbdDaemon::Start() 24 | { 25 | DWORD err = WnbdCreate( 26 | WnbdProps, (const PWNBD_INTERFACE) &MockWnbdInterface, 27 | this, &WnbdDisk); 28 | ASSERT_FALSE(err) << "WnbdCreate failed"; 29 | 30 | Started = true; 31 | 32 | err = WnbdStartDispatcher(WnbdDisk, IO_REQ_WORKERS); 33 | ASSERT_FALSE(err) << "WnbdStartDispatcher failed"; 34 | 35 | if (!WnbdProps->Flags.ReadOnly) { 36 | std::string InstanceName = WnbdProps->InstanceName; 37 | SetDiskWritable(InstanceName); 38 | } 39 | } 40 | 41 | void MockWnbdDaemon::Shutdown() 42 | { 43 | std::unique_lock Lock(ShutdownLock); 44 | if (!Terminated && WnbdDisk) { 45 | TerminateInProgress = true; 46 | // We're requesting the disk to be removed but continue serving IO 47 | // requests until the driver sends us the "Disconnect" event. 48 | DWORD Ret = WnbdRemove(WnbdDisk, NULL); 49 | if (Ret && Ret != ERROR_FILE_NOT_FOUND) 50 | ASSERT_FALSE(Ret) << "couldn't stop the wnbd dispatcher, err: " << Ret; 51 | Wait(); 52 | Terminated = true; 53 | } 54 | } 55 | 56 | void MockWnbdDaemon::Wait() 57 | { 58 | if (Started && WnbdDisk) { 59 | DWORD err = WnbdWaitDispatcher(WnbdDisk); 60 | ASSERT_FALSE(err) << "failed waiting for the dispatcher to stop"; 61 | } 62 | } 63 | 64 | void MockWnbdDaemon::Read( 65 | PWNBD_DISK Disk, 66 | UINT64 RequestHandle, 67 | PVOID Buffer, 68 | UINT64 BlockAddress, 69 | UINT32 BlockCount, 70 | BOOLEAN ForceUnitAccess) 71 | { 72 | MockWnbdDaemon* handler = nullptr; 73 | ASSERT_FALSE(WnbdGetUserContext(Disk, (PVOID*)&handler)); 74 | 75 | ASSERT_TRUE(handler->WnbdProps->BlockSize); 76 | ASSERT_TRUE(handler->WnbdProps->BlockSize * BlockCount 77 | <= WNBD_DEFAULT_MAX_TRANSFER_LENGTH); 78 | 79 | WnbdRequestType RequestType = WnbdReqTypeRead; 80 | WNBD_IO_REQUEST WnbdReq = { 0 }; 81 | WnbdReq.RequestType = RequestType; 82 | WnbdReq.RequestHandle = RequestHandle; 83 | WnbdReq.Cmd.Read.BlockAddress = BlockAddress; 84 | WnbdReq.Cmd.Read.BlockCount = BlockCount; 85 | WnbdReq.Cmd.Read.ForceUnitAccess = ForceUnitAccess; 86 | 87 | handler->ReqLog.AddEntry(WnbdReq); 88 | 89 | if (Disk->Properties.BlockCount < BlockAddress + BlockCount) { 90 | // Overflow 91 | WnbdSetSense( 92 | &handler->MockStatus, 93 | SCSI_SENSE_ILLEGAL_REQUEST, 94 | SCSI_ADSENSE_ILLEGAL_BLOCK); 95 | } else { 96 | memset(Buffer, READ_BYTE_CONTENT, BlockCount * handler->WnbdProps->BlockSize); 97 | } 98 | 99 | handler->SendIoResponse( 100 | RequestHandle, RequestType, 101 | handler->MockStatus, 102 | Buffer, handler->WnbdProps->BlockSize * BlockCount); 103 | } 104 | 105 | void MockWnbdDaemon::Write( 106 | PWNBD_DISK Disk, 107 | UINT64 RequestHandle, 108 | PVOID Buffer, 109 | UINT64 BlockAddress, 110 | UINT32 BlockCount, 111 | BOOLEAN ForceUnitAccess) 112 | { 113 | MockWnbdDaemon* handler = nullptr; 114 | ASSERT_FALSE(WnbdGetUserContext(Disk, (PVOID*)&handler)); 115 | 116 | ASSERT_TRUE(handler->WnbdProps->BlockSize); 117 | ASSERT_TRUE(handler->WnbdProps->BlockSize * BlockCount 118 | <= WNBD_DEFAULT_MAX_TRANSFER_LENGTH); 119 | 120 | WnbdRequestType RequestType = WnbdReqTypeWrite; 121 | WNBD_IO_REQUEST WnbdReq = { 0 }; 122 | WnbdReq.RequestType = RequestType; 123 | WnbdReq.RequestHandle = RequestHandle; 124 | WnbdReq.Cmd.Write.BlockAddress = BlockAddress; 125 | WnbdReq.Cmd.Write.BlockCount = BlockCount; 126 | WnbdReq.Cmd.Write.ForceUnitAccess = ForceUnitAccess; 127 | 128 | handler->ReqLog.AddEntry(WnbdReq, Buffer, BlockCount * handler->WnbdProps->BlockSize); 129 | 130 | WNBD_STATUS Status = handler->MockStatus; 131 | 132 | if (Disk->Properties.BlockCount < BlockAddress + BlockCount) { 133 | // Overflow 134 | // TODO: consider moving this check to the driver. 135 | WnbdSetSense( 136 | &Status, 137 | SCSI_SENSE_ILLEGAL_REQUEST, 138 | SCSI_ADSENSE_VOLUME_OVERFLOW); 139 | } 140 | 141 | handler->SendIoResponse( 142 | RequestHandle, RequestType, 143 | Status, 144 | Buffer, handler->WnbdProps->BlockSize * BlockCount); 145 | } 146 | 147 | void MockWnbdDaemon::Flush( 148 | PWNBD_DISK Disk, 149 | UINT64 RequestHandle, 150 | UINT64 BlockAddress, 151 | UINT32 BlockCount) 152 | { 153 | MockWnbdDaemon* handler = nullptr; 154 | ASSERT_FALSE(WnbdGetUserContext(Disk, (PVOID*)&handler)); 155 | 156 | WnbdRequestType RequestType = WnbdReqTypeFlush; 157 | WNBD_IO_REQUEST WnbdReq = { 0 }; 158 | WnbdReq.RequestType = RequestType; 159 | WnbdReq.RequestHandle = RequestHandle; 160 | WnbdReq.Cmd.Flush.BlockAddress = BlockAddress; 161 | WnbdReq.Cmd.Flush.BlockCount = BlockCount; 162 | 163 | handler->ReqLog.AddEntry(WnbdReq); 164 | 165 | if (Disk->Properties.BlockCount < BlockAddress + BlockCount) { 166 | // Overflow 167 | // TODO: consider moving this check to the driver. 168 | WnbdSetSense( 169 | &handler->MockStatus, 170 | SCSI_SENSE_ILLEGAL_REQUEST, 171 | SCSI_ADSENSE_VOLUME_OVERFLOW); 172 | } 173 | 174 | handler->SendIoResponse( 175 | RequestHandle, RequestType, 176 | handler->MockStatus, NULL, 0); 177 | } 178 | 179 | void MockWnbdDaemon::Unmap( 180 | PWNBD_DISK Disk, 181 | UINT64 RequestHandle, 182 | PWNBD_UNMAP_DESCRIPTOR Descriptors, 183 | UINT32 Count) 184 | { 185 | MockWnbdDaemon* handler = nullptr; 186 | ASSERT_FALSE(WnbdGetUserContext(Disk, (PVOID*)&handler)); 187 | 188 | WnbdRequestType RequestType = WnbdReqTypeUnmap; 189 | WNBD_IO_REQUEST WnbdReq = { 0 }; 190 | WnbdReq.RequestType = RequestType; 191 | WnbdReq.RequestHandle = RequestHandle; 192 | WnbdReq.Cmd.Unmap.Count = Count; 193 | 194 | handler->ReqLog.AddEntry( 195 | WnbdReq, 196 | (void*)Descriptors, 197 | sizeof(WNBD_UNMAP_DESCRIPTOR) * Count); 198 | 199 | // TODO: validate unmap descriptors 200 | 201 | handler->SendIoResponse( 202 | RequestHandle, RequestType, 203 | handler->MockStatus, NULL, 0); 204 | } 205 | 206 | void MockWnbdDaemon::PersistentReserveIn( 207 | PWNBD_DISK Disk, 208 | UINT64 RequestHandle, 209 | UINT8 ServiceAction) 210 | { 211 | MockWnbdDaemon* handler = nullptr; 212 | ASSERT_FALSE(WnbdGetUserContext(Disk, (PVOID*)&handler)); 213 | 214 | WnbdRequestType RequestType = WnbdReqTypePersistResIn; 215 | WNBD_IO_REQUEST WnbdReq = { 0 }; 216 | WnbdReq.RequestType = RequestType; 217 | WnbdReq.RequestHandle = RequestHandle; 218 | WnbdReq.Cmd.PersistResIn.ServiceAction = ServiceAction; 219 | 220 | handler->ReqLog.AddEntry(WnbdReq); 221 | 222 | switch (ServiceAction) 223 | { 224 | case RESERVATION_ACTION_READ_KEYS: 225 | case RESERVATION_ACTION_READ_RESERVATIONS: 226 | break; 227 | default: 228 | WnbdSetSense( 229 | &handler->MockStatus, 230 | SCSI_SENSE_ILLEGAL_REQUEST, 231 | SCSI_ADSENSE_ILLEGAL_COMMAND); 232 | break; 233 | } 234 | 235 | MOCK_PRI_LIST MockPrList = { MOCK_PR_GENERATION, 0 }; 236 | handler->SendIoResponse( 237 | RequestHandle, RequestType, 238 | handler->MockStatus, &MockPrList, sizeof(MockPrList)); 239 | } 240 | 241 | void MockWnbdDaemon::PersistentReserveOut( 242 | PWNBD_DISK Disk, 243 | UINT64 RequestHandle, 244 | UINT8 ServiceAction, 245 | UINT8 Scope, 246 | UINT8 Type, 247 | PVOID Buffer, 248 | UINT32 ParameterListLength) 249 | { 250 | MockWnbdDaemon* handler = nullptr; 251 | ASSERT_FALSE(WnbdGetUserContext(Disk, (PVOID*)&handler)); 252 | 253 | WnbdRequestType RequestType = WnbdReqTypePersistResOut; 254 | WNBD_IO_REQUEST WnbdReq = { 0 }; 255 | WnbdReq.RequestType = RequestType; 256 | WnbdReq.RequestHandle = RequestHandle; 257 | WnbdReq.Cmd.PersistResOut.ServiceAction = ServiceAction; 258 | WnbdReq.Cmd.PersistResOut.ParameterListLength = ParameterListLength; 259 | WnbdReq.Cmd.PersistResOut.Scope = Scope; 260 | WnbdReq.Cmd.PersistResOut.Type = Type; 261 | 262 | handler->ReqLog.AddEntry(WnbdReq, Buffer, ParameterListLength); 263 | 264 | switch (ServiceAction) 265 | { 266 | case RESERVATION_ACTION_REGISTER: 267 | case RESERVATION_ACTION_REGISTER_IGNORE_EXISTING: 268 | case RESERVATION_ACTION_RESERVE: 269 | case RESERVATION_ACTION_RELEASE: 270 | case RESERVATION_ACTION_CLEAR: 271 | case RESERVATION_ACTION_PREEMPT: 272 | break; 273 | default: 274 | WnbdSetSense( 275 | &handler->MockStatus, 276 | SCSI_SENSE_ILLEGAL_REQUEST, 277 | SCSI_ADSENSE_ILLEGAL_COMMAND); 278 | break; 279 | } 280 | 281 | handler->SendIoResponse( 282 | RequestHandle, RequestType, 283 | handler->MockStatus, NULL, 0); 284 | } 285 | 286 | void MockWnbdDaemon::SendIoResponse( 287 | UINT64 RequestHandle, 288 | WnbdRequestType RequestType, 289 | WNBD_STATUS& Status, 290 | PVOID DataBuffer, 291 | UINT32 DataBufferSize 292 | ) { 293 | ASSERT_TRUE(WNBD_DEFAULT_MAX_TRANSFER_LENGTH >= DataBufferSize) 294 | << "wnbd response too large"; 295 | 296 | WNBD_IO_RESPONSE Resp = { 0 }; 297 | Resp.RequestHandle = RequestHandle; 298 | Resp.RequestType = RequestType; 299 | Resp.Status = Status; 300 | 301 | int err = WnbdSendResponse( 302 | WnbdDisk, 303 | &Resp, 304 | DataBuffer, 305 | DataBufferSize); 306 | 307 | if (err && TerminateInProgress) { 308 | // Suppress errors that might occur because of pending disk removals. 309 | err = 0; 310 | } 311 | 312 | ASSERT_FALSE(err) << "unable to send wnbd response, error: " 313 | << GetLastError(); 314 | } 315 | 316 | PWNBD_DISK MockWnbdDaemon::GetDisk() { 317 | return WnbdDisk; 318 | } 319 | 320 | void MockWnbdDaemon::TerminatingInProgress() { 321 | TerminateInProgress = true; 322 | } 323 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/mock_wnbd_daemon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "request_log.h" 12 | 13 | #define IO_REQ_WORKERS 2 14 | 15 | // The following byte will be used to fill read buffers, 16 | // allowing us to make assertions. 17 | #define READ_BYTE_CONTENT 0x0f 18 | #define WRITE_BYTE_CONTENT 0x0a 19 | 20 | #define MOCK_PR_GENERATION 0xf1e2 21 | 22 | class MockWnbdDaemon 23 | { 24 | private: 25 | PWNBD_PROPERTIES WnbdProps; 26 | 27 | public: 28 | MockWnbdDaemon(PWNBD_PROPERTIES _WnbdProps) : WnbdProps(_WnbdProps) {}; 29 | ~MockWnbdDaemon(); 30 | 31 | void Start(); 32 | // Wait for the daemon to stop, which normally happens when the driver 33 | // passes the "Disconnect" request. 34 | void Wait(); 35 | void Shutdown(); 36 | 37 | void SetStatus(WNBD_STATUS& Status) { 38 | MockStatus = Status; 39 | } 40 | 41 | RequestLog ReqLog; 42 | 43 | private: 44 | bool Started = false; 45 | bool Terminated = false; 46 | bool TerminateInProgress = false; 47 | PWNBD_DISK WnbdDisk = nullptr; 48 | 49 | WNBD_STATUS MockStatus = { 0 }; 50 | 51 | std::mutex ShutdownLock; 52 | 53 | // WNBD IO entry points 54 | static void Read( 55 | PWNBD_DISK Disk, 56 | UINT64 RequestHandle, 57 | PVOID Buffer, 58 | UINT64 BlockAddress, 59 | UINT32 BlockCount, 60 | BOOLEAN ForceUnitAccess); 61 | static void Write( 62 | PWNBD_DISK Disk, 63 | UINT64 RequestHandle, 64 | PVOID Buffer, 65 | UINT64 BlockAddress, 66 | UINT32 BlockCount, 67 | BOOLEAN ForceUnitAccess); 68 | static void Flush( 69 | PWNBD_DISK Disk, 70 | UINT64 RequestHandle, 71 | UINT64 BlockAddress, 72 | UINT32 BlockCount); 73 | static void Unmap( 74 | PWNBD_DISK Disk, 75 | UINT64 RequestHandle, 76 | PWNBD_UNMAP_DESCRIPTOR Descriptors, 77 | UINT32 Count); 78 | static void PersistentReserveIn( 79 | PWNBD_DISK Disk, 80 | UINT64 RequestHandle, 81 | UINT8 ServiceAction); 82 | static void PersistentReserveOut( 83 | PWNBD_DISK Disk, 84 | UINT64 RequestHandle, 85 | UINT8 ServiceAction, 86 | UINT8 Scope, 87 | UINT8 Type, 88 | PVOID Buffer, 89 | UINT32 ParameterListLength); 90 | 91 | static constexpr WNBD_INTERFACE MockWnbdInterface = 92 | { 93 | Read, 94 | Write, 95 | Flush, 96 | Unmap, 97 | PersistentReserveIn, 98 | PersistentReserveOut 99 | }; 100 | 101 | void SendIoResponse( 102 | UINT64 RequestHandle, 103 | WnbdRequestType RequestType, 104 | WNBD_STATUS& Status, 105 | PVOID DataBuffer, 106 | UINT32 DataBufferSize 107 | ); 108 | public: 109 | PWNBD_DISK GetDisk(); 110 | 111 | void TerminatingInProgress(); 112 | }; 113 | 114 | // We're stubbing READ_KEYS and READ_RESERVATIONS PR actions, both of which 115 | // use the following header. 116 | typedef struct { 117 | UINT32 Generation; 118 | UINT32 AdditionalLength; 119 | } MOCK_PRI_LIST, *PMOCK_PRI_LIST; 120 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/options.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "pch.h" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace po = boost::program_options; 15 | using namespace std; 16 | 17 | po::variables_map Vm; 18 | 19 | void AddCommonOptions( 20 | po::positional_options_description &PosOpts, 21 | po::options_description &NamedOpts) 22 | { 23 | NamedOpts.add_options() 24 | ("log-level", po::value()->default_value(WnbdLogLevelWarning), 25 | "Wnbd log level.") 26 | ("help", po::bool_switch(), "Show usage."); 27 | } 28 | 29 | void AddNbdOptions( 30 | po::positional_options_description &PosOpts, 31 | po::options_description &NamedOpts) 32 | { 33 | NamedOpts.add_options() 34 | ("nbd-export-name", po::value(), 35 | "NBD export name") 36 | ("nbd-hostname", po::value(), 37 | "NBD server hostname.") 38 | ("nbd-port", po::value()->default_value(10809), 39 | "NBD server port number. Default: 10809."); 40 | } 41 | 42 | void ParseOptions(int Argc, char** Argv) 43 | { 44 | po::positional_options_description PosOpts; 45 | po::options_description NamedOpts; 46 | 47 | vector Args; 48 | Args.insert(Args.end(), Argv + 1, Argv + Argc); 49 | 50 | try { 51 | AddCommonOptions(PosOpts, NamedOpts); 52 | AddNbdOptions(PosOpts, NamedOpts); 53 | 54 | po::store(po::command_line_parser(Args) 55 | .options(NamedOpts) 56 | .positional(PosOpts) 57 | .run(), Vm); 58 | po::notify(Vm); 59 | } 60 | catch (po::required_option& e) { 61 | cerr << "libwnbd_tests: " << e.what() << endl; 62 | exit(-1); 63 | } 64 | catch (po::too_many_positional_options_error&) { 65 | cerr << "libwnbd_tests: too many arguments" << endl; 66 | exit(-1); 67 | } 68 | catch (po::error& e) { 69 | cerr << "libwnbd_tests: " << e.what() << endl; 70 | exit(-1); 71 | } 72 | catch (...) { 73 | cerr << "libwnbd_tests: Caught unexpected exception." << endl << endl; 74 | cerr << boost::current_exception_diagnostic_information() << endl; 75 | exit(-1); 76 | } 77 | } 78 | 79 | // TODO: consider using the helpers from usage.cpp if we ever end up having 80 | // an internal library reused between libwnbd, wnbd-client and the tests. 81 | void PrintHelp() 82 | { 83 | const char* Msg = R"HELP( 84 | Wnbd test parameters: 85 | --log-level=[LOG_LEVEL] 86 | Wnbd log level 87 | 88 | --nbd-export-name=[NBD_EXPORT_NAME] 89 | Existing NBD export to be used for NBD tests. 90 | If unspecified, NBD tests will be skipped. 91 | --nbd-hostname=[NBD_HOSTNAME] 92 | The NBD server address. 93 | --nbd-port=[NBD_PORT] 94 | The NBD server port. Default: 10809. 95 | )HELP"; 96 | 97 | cout << Msg << endl; 98 | } 99 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/options.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace po = boost::program_options; 14 | 15 | extern po::variables_map Vm; 16 | 17 | // Make sure to pass the EXACT SAME type that was used when defining the option, 18 | // otherwise Boost will throw an exception. 19 | template 20 | T GetOpt(std::string Name, T DefaultVal = T()) 21 | { 22 | if (Vm.count(Name)) { 23 | return Vm[Name].as(); 24 | } 25 | return DefaultVal; 26 | } 27 | 28 | void ParseOptions(int Argc, char** Argv); 29 | void PrintHelp(); 30 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/pch.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // pch.cpp 3 | // 4 | 5 | #include "pch.h" 6 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/pch.h: -------------------------------------------------------------------------------- 1 | // 2 | // pch.h 3 | // 4 | 5 | #pragma once 6 | 7 | #define _CRT_RAND_S 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #define _NTSCSI_USER_MODE_ 15 | #include 16 | 17 | #include "gtest/gtest.h" 18 | 19 | #include 20 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/request_log.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "pch.h" 8 | 9 | #include "request_log.h" 10 | 11 | void RequestLog::PushBack(const RequestLogEntry& Entry) 12 | { 13 | std::unique_lock Lock(ListLock); 14 | 15 | auto RequestHandle = Entry.WnbdRequest.RequestHandle; 16 | 17 | // Validate the request handle. 18 | auto Result = RequestHandles.insert(RequestHandle); 19 | ASSERT_TRUE(Result.second) 20 | << "duplicate request handle received : " 21 | << RequestHandle; 22 | 23 | Entries.push_back(Entry); 24 | } 25 | 26 | void RequestLog::AddEntry( 27 | WNBD_IO_REQUEST& WnbdRequest, 28 | void* DataBuffer, 29 | size_t DataBufferSize) 30 | { 31 | void* BufferCopy = nullptr; 32 | size_t BufferCopySize = 0; 33 | 34 | if (DataBuffer && DataBufferSize > 0) { 35 | BufferCopySize = DataBufferSize; 36 | BufferCopy = malloc(BufferCopySize); 37 | ASSERT_TRUE(BufferCopy) 38 | << "couldn't allocate buffer, size: " << BufferCopySize; 39 | memcpy(BufferCopy, DataBuffer, BufferCopySize); 40 | } 41 | 42 | RequestLogEntry Entry(WnbdRequest, BufferCopy, BufferCopySize); 43 | PushBack(Entry); 44 | } 45 | 46 | bool RequestLog::HasEntry( 47 | WNBD_IO_REQUEST& ExpWnbdRequest, 48 | void* DataBuffer, 49 | size_t DataBufferSize, 50 | bool CheckDataBuffer) 51 | { 52 | std::unique_lock Lock(ListLock); 53 | 54 | for (auto Entry: Entries) { 55 | // Skip the request handle when comparing the requests 56 | if (!memcmp((char*) &ExpWnbdRequest + sizeof(ExpWnbdRequest.RequestHandle), 57 | (char*) &Entry + sizeof(ExpWnbdRequest.RequestHandle), 58 | sizeof(WNBD_IO_REQUEST) - sizeof(ExpWnbdRequest.RequestHandle))) { 59 | if (CheckDataBuffer) { 60 | if (!Entry.DataBuffer.get()) { 61 | continue; 62 | } 63 | 64 | // Found a matching request, let's compare the buffers. 65 | if (Entry.DataBufferSize != DataBufferSize) { 66 | continue; 67 | } 68 | 69 | if (memcmp(DataBuffer, Entry.DataBuffer.get(), DataBufferSize)) { 70 | continue; 71 | } 72 | } 73 | 74 | // Found the expected request 75 | return true; 76 | } 77 | } 78 | return false; 79 | } 80 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/request_log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | struct RequestLogEntry 16 | { 17 | WNBD_IO_REQUEST WnbdRequest; 18 | std::shared_ptr DataBuffer; 19 | size_t DataBufferSize; 20 | 21 | RequestLogEntry( 22 | WNBD_IO_REQUEST _WnbdRequest, 23 | void* _DataBuffer, 24 | size_t _DataBufferSize) 25 | : WnbdRequest(_WnbdRequest) 26 | , DataBuffer(std::shared_ptr(_DataBuffer, free)) 27 | , DataBufferSize(_DataBufferSize) 28 | {} 29 | 30 | RequestLogEntry(WNBD_IO_REQUEST _WnbdRequest) 31 | : WnbdRequest(_WnbdRequest) 32 | , DataBuffer(nullptr) 33 | , DataBufferSize(0) 34 | {} 35 | }; 36 | 37 | struct RequestLog 38 | { 39 | std::mutex ListLock; 40 | std::list Entries; 41 | 42 | // We'll ensure that there are no duplicate request handles 43 | std::set RequestHandles; 44 | 45 | void PushBack(const RequestLogEntry& Entry); 46 | 47 | // Adds the specified Entry to the request log. If a buffer is specified, 48 | // we'll make a copy. 49 | void AddEntry( 50 | WNBD_IO_REQUEST& WnbdRequest, 51 | void* DataBuffer, 52 | size_t DataBufferSize); 53 | 54 | void AddEntry(WNBD_IO_REQUEST& WnbdRequest) 55 | { 56 | RequestLogEntry Entry(WnbdRequest, nullptr, 0); 57 | PushBack(Entry); 58 | } 59 | 60 | bool HasEntry( 61 | WNBD_IO_REQUEST& ExpWnbdRequest, 62 | void* DataBuffer, 63 | size_t DataBufferSize, 64 | bool CheckDataBuffer); 65 | 66 | // Searches for the specified request, ignoring the request handle. 67 | bool HasEntry( 68 | WNBD_IO_REQUEST& ExpWnbdRequest, 69 | void* DataBuffer, 70 | size_t DataBufferSize) 71 | { 72 | return HasEntry(ExpWnbdRequest, DataBuffer, DataBufferSize, true); 73 | } 74 | 75 | // Searches for the specified request, ignoring the request handle 76 | // and data buffers. 77 | bool HasEntry( 78 | WNBD_IO_REQUEST& ExpWnbdRequest) 79 | { 80 | return HasEntry(ExpWnbdRequest, nullptr, 0, false); 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "pch.h" 8 | 9 | #include "utils.h" 10 | #include "options.h" 11 | 12 | int main(int argc, char **argv) 13 | { 14 | if (argc > 1 && !strcmp(argv[1], "--help")) { 15 | PrintHelp(); 16 | } 17 | 18 | ::testing::InitGoogleTest(&argc, argv); 19 | 20 | ParseOptions(argc, argv); 21 | 22 | DWORD LogLevel = GetOpt("log-level"); 23 | WnbdSetLogLevel((WnbdLogLevel) LogLevel); 24 | 25 | if (int Err = InitializeWinsock()) { 26 | return Err; 27 | } 28 | 29 | return RUN_ALL_TESTS(); 30 | } 31 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/test_adapter_actions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "pch.h" 8 | #include "mock_wnbd_daemon.h" 9 | #include "utils.h" 10 | 11 | // The following test covers get/set/reset operations for both 12 | // ephemeral and persistent settings. Normally I'd avoid such a 13 | // long test, however it allows us to transition through various 14 | // scenarios. 15 | TEST(TestDrvOptions, TestGetSetDrvOptInt64) { 16 | WNBD_OPTION_VALUE OptVal = { WnbdOptUnknown, 0 }; 17 | 18 | // 1. Reset the option 19 | // ------------------- 20 | DWORD Status = WnbdResetDrvOpt("LogLevel", TRUE); 21 | ASSERT_TRUE(!Status || Status == ERROR_FILE_NOT_FOUND) 22 | << "couldn't reset opt"; 23 | 24 | // retrieve persistent setting 25 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, TRUE); 26 | // the persistent setting was cleared out, so we expect it 27 | // to be unset. 28 | ASSERT_EQ(ERROR_FILE_NOT_FOUND, Status) 29 | << "couldn't retrieve persistent opt"; 30 | 31 | // retrieve current non-persistent setting 32 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, FALSE); 33 | ASSERT_FALSE(Status) << "couldn't retrieve opt"; 34 | 35 | ASSERT_EQ(OptVal.Type, WnbdOptInt64); 36 | // the default value 37 | ASSERT_EQ(OptVal.Data.AsInt64, 1); 38 | 39 | // 2. Set a non-persistent value 40 | // ----------------------------- 41 | OptVal.Data.AsInt64 = 2; 42 | Status = WnbdSetDrvOpt("LogLevel", &OptVal, FALSE); 43 | ASSERT_FALSE(Status) << "couldn't set opt"; 44 | 45 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, TRUE); 46 | // no persistent setting yet 47 | ASSERT_EQ(ERROR_FILE_NOT_FOUND, Status); 48 | 49 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, FALSE); 50 | ASSERT_FALSE(Status) << "couldn't retrieve opt"; 51 | ASSERT_EQ(OptVal.Type, WnbdOptInt64); 52 | ASSERT_EQ(OptVal.Data.AsInt64, 2); 53 | 54 | // 3. Set a persistent value 55 | // ------------------------- 56 | OptVal.Data.AsInt64 = 3; 57 | Status = WnbdSetDrvOpt("LogLevel", &OptVal, TRUE); 58 | ASSERT_FALSE(Status) << "couldn't set opt"; 59 | 60 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, TRUE); 61 | ASSERT_FALSE(Status) << "couldn't retrieve opt"; 62 | ASSERT_EQ(OptVal.Type, WnbdOptInt64); 63 | ASSERT_EQ(OptVal.Data.AsInt64, 3); 64 | 65 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, FALSE); 66 | ASSERT_FALSE(Status) << "couldn't retrieve opt"; 67 | ASSERT_EQ(OptVal.Type, WnbdOptInt64); 68 | ASSERT_EQ(OptVal.Data.AsInt64, 3); 69 | 70 | // 4. Set an ephemeral value, masking the persistent one 71 | // ----------------------------------------------------- 72 | OptVal.Data.AsInt64 = 4; 73 | Status = WnbdSetDrvOpt("LogLevel", &OptVal, FALSE); 74 | ASSERT_FALSE(Status) << "couldn't set opt"; 75 | 76 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, TRUE); 77 | ASSERT_FALSE(Status) << "couldn't retrieve opt"; 78 | ASSERT_EQ(OptVal.Type, WnbdOptInt64); 79 | // We've retrieved the original persistent value 80 | ASSERT_EQ(OptVal.Data.AsInt64, 3); 81 | 82 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, FALSE); 83 | ASSERT_FALSE(Status) << "couldn't retrieve opt"; 84 | ASSERT_EQ(OptVal.Type, WnbdOptInt64); 85 | // We're now checking the current ephemeral value 86 | ASSERT_EQ(OptVal.Data.AsInt64, 4); 87 | 88 | // 5. Reset the ephemeral value 89 | // ---------------------------- 90 | Status = WnbdResetDrvOpt("LogLevel", FALSE); 91 | ASSERT_FALSE(Status) << "couldn't reset opt"; 92 | 93 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, TRUE); 94 | ASSERT_FALSE(Status) << "couldn't retrieve opt"; 95 | ASSERT_EQ(OptVal.Type, WnbdOptInt64); 96 | // We've retrieved the original persistent value 97 | ASSERT_EQ(OptVal.Data.AsInt64, 3); 98 | 99 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, FALSE); 100 | ASSERT_FALSE(Status) << "couldn't retrieve opt"; 101 | ASSERT_EQ(OptVal.Type, WnbdOptInt64); 102 | // We're now checking the current ephemeral value 103 | ASSERT_EQ(OptVal.Data.AsInt64, 1); 104 | 105 | // 5. Reset the persistent value, back to square one 106 | // ------------------------------------------------- 107 | Status = WnbdResetDrvOpt("LogLevel", TRUE); 108 | ASSERT_FALSE(Status) << "couldn't reset opt"; 109 | 110 | // retrieve persistent setting 111 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, TRUE); 112 | // the persistent setting was cleared out, so we expect it 113 | // to be unset. 114 | ASSERT_EQ(ERROR_FILE_NOT_FOUND, Status) 115 | << "couldn't retrieve persistent opt"; 116 | 117 | // retrieve current non-persistent setting 118 | Status = WnbdGetDrvOpt("LogLevel", &OptVal, FALSE); 119 | ASSERT_FALSE(Status) << "couldn't retrieve opt"; 120 | 121 | ASSERT_EQ(OptVal.Type, WnbdOptInt64); 122 | // the default value 123 | ASSERT_EQ(OptVal.Data.AsInt64, 1); 124 | } 125 | 126 | TEST(TestDrvOptions, TestListOptions) { 127 | // 1. Reset the option 128 | // ------------------- 129 | DWORD Status = WnbdResetDrvOpt("LogLevel", TRUE); 130 | ASSERT_TRUE(!Status || Status == ERROR_FILE_NOT_FOUND) 131 | << "couldn't reset opt"; 132 | 133 | Status = WnbdResetDrvOpt("DbgPrintEnabled", TRUE); 134 | ASSERT_TRUE(!Status || Status == ERROR_FILE_NOT_FOUND) 135 | << "couldn't reset opt"; 136 | 137 | Status = WnbdResetDrvOpt("EtwLoggingEnabled", TRUE); 138 | ASSERT_TRUE(!Status || Status == ERROR_FILE_NOT_FOUND) 139 | << "couldn't reset opt"; 140 | 141 | // 2. Modify some of the options 142 | // ----------------------------- 143 | WNBD_OPTION_VALUE OptVal = { WnbdOptUnknown, 0 }; 144 | 145 | OptVal.Type = WnbdOptInt64; 146 | OptVal.Data.AsInt64 = 3; 147 | Status = WnbdSetDrvOpt("LogLevel", &OptVal, TRUE); 148 | ASSERT_FALSE(Status) << "couldn't set opt"; 149 | 150 | OptVal.Type = WnbdOptInt64; 151 | OptVal.Data.AsInt64 = 2; 152 | Status = WnbdSetDrvOpt("LogLevel", &OptVal, FALSE); 153 | ASSERT_FALSE(Status) << "couldn't set opt"; 154 | 155 | OptVal.Type = WnbdOptBool; 156 | OptVal.Data.AsBool = FALSE; 157 | Status = WnbdSetDrvOpt("DbgPrintEnabled", &OptVal, FALSE); 158 | ASSERT_FALSE(Status) << "couldn't set opt"; 159 | 160 | OptVal.Type = WnbdOptBool; 161 | OptVal.Data.AsBool = TRUE; 162 | Status = WnbdSetDrvOpt("EtwLoggingEnabled", &OptVal, TRUE); 163 | ASSERT_FALSE(Status) << "couldn't set opt"; 164 | 165 | // 3. Check current options 166 | // ------------------------ 167 | WnbdOptionList OptList = WnbdOptionList(); 168 | Status = OptList.Retrieve(FALSE); 169 | ASSERT_FALSE(Status) << "couldn't list opt"; 170 | 171 | PWNBD_OPTION Option = OptList.GetOpt(L"LogLevel"); 172 | ASSERT_TRUE(Option) << "couldn't retrieve opt"; 173 | ASSERT_EQ(Option->Type, WnbdOptInt64); 174 | ASSERT_EQ(Option->Default.Type, WnbdOptInt64); 175 | ASSERT_EQ(Option->Default.Data.AsInt64, 1); 176 | ASSERT_EQ(Option->Value.Type, WnbdOptInt64); 177 | ASSERT_EQ(Option->Value.Data.AsInt64, 2); 178 | 179 | Option = OptList.GetOpt(L"DbgPrintEnabled"); 180 | ASSERT_TRUE(Option) << "couldn't retrieve opt"; 181 | ASSERT_EQ(Option->Type, WnbdOptBool); 182 | ASSERT_EQ(Option->Default.Type, WnbdOptBool); 183 | ASSERT_TRUE(Option->Default.Data.AsBool); 184 | ASSERT_EQ(Option->Value.Type, WnbdOptBool); 185 | ASSERT_FALSE(Option->Value.Data.AsBool); 186 | 187 | Option = OptList.GetOpt(L"EtwLoggingEnabled"); 188 | ASSERT_TRUE(Option) << "couldn't retrieve opt"; 189 | ASSERT_EQ(Option->Type, WnbdOptBool); 190 | ASSERT_EQ(Option->Default.Type, WnbdOptBool); 191 | ASSERT_TRUE(Option->Default.Data.AsBool); 192 | ASSERT_EQ(Option->Value.Type, WnbdOptBool); 193 | ASSERT_TRUE(Option->Value.Data.AsBool); 194 | 195 | // 4. Check persistent options 196 | // --------------------------- 197 | Status = OptList.Retrieve(TRUE); 198 | ASSERT_FALSE(Status) << "couldn't list opt"; 199 | 200 | Option = OptList.GetOpt(L"LogLevel"); 201 | ASSERT_TRUE(Option) << "couldn't retrieve opt"; 202 | ASSERT_EQ(Option->Type, WnbdOptInt64); 203 | ASSERT_EQ(Option->Default.Type, WnbdOptInt64); 204 | ASSERT_EQ(Option->Default.Data.AsInt64, 1); 205 | ASSERT_EQ(Option->Value.Type, WnbdOptInt64); 206 | ASSERT_EQ(Option->Value.Data.AsInt64, 3); 207 | 208 | Option = OptList.GetOpt(L"EtwLoggingEnabled"); 209 | ASSERT_TRUE(Option) << "couldn't retrieve opt"; 210 | ASSERT_EQ(Option->Type, WnbdOptBool); 211 | ASSERT_EQ(Option->Default.Type, WnbdOptBool); 212 | ASSERT_TRUE(Option->Default.Data.AsBool); 213 | ASSERT_EQ(Option->Value.Type, WnbdOptBool); 214 | ASSERT_TRUE(Option->Value.Data.AsBool); 215 | 216 | Option = OptList.GetOpt(L"DbgPrintEnabled"); 217 | ASSERT_FALSE(Option) 218 | << "the persistent list contains an option that isn't persistent"; 219 | 220 | // 5. Reset modified options 221 | // ------------------------- 222 | Status = WnbdResetDrvOpt("LogLevel", TRUE); 223 | ASSERT_TRUE(!Status || Status == ERROR_FILE_NOT_FOUND) 224 | << "couldn't reset opt"; 225 | 226 | Status = WnbdResetDrvOpt("DbgPrintEnabled", TRUE); 227 | ASSERT_TRUE(!Status || Status == ERROR_FILE_NOT_FOUND) 228 | << "couldn't reset opt"; 229 | 230 | Status = WnbdResetDrvOpt("EtwLoggingEnabled", TRUE); 231 | ASSERT_TRUE(!Status || Status == ERROR_FILE_NOT_FOUND) 232 | << "couldn't reset opt"; 233 | } 234 | 235 | TEST(TestDrvOptions, TestIOLimits) { 236 | WNBD_OPTION_VALUE OptVal = { WnbdOptUnknown, 0 }; 237 | DWORD Status = 0; 238 | 239 | // We'll create a mapping in order to validate the IO limits. 240 | WNBD_PROPERTIES WnbdProps = { 0 }; 241 | GetNewWnbdProps(&WnbdProps); 242 | 243 | DWORD CustomAdapterIOLimit = 8192; 244 | DWORD CustomLunIOLimit = 512; 245 | 246 | DWORD RetrievedAdapterIOLimit = 0; 247 | DWORD RetrievedLunIOLimit = 0; 248 | 249 | OptVal.Type = WnbdOptInt64; 250 | OptVal.Data.AsInt64 = CustomAdapterIOLimit; 251 | Status = WnbdSetDrvOpt("MaxIOReqPerAdapter", &OptVal, TRUE); 252 | ASSERT_FALSE(Status) << "couldn't set opt"; 253 | 254 | OptVal.Type = WnbdOptInt64; 255 | OptVal.Data.AsInt64 = CustomLunIOLimit; 256 | Status = WnbdSetDrvOpt("MaxIOReqPerLun", &OptVal, TRUE); 257 | ASSERT_FALSE(Status) << "couldn't set opt"; 258 | 259 | // Reset the adapter in order to load the new settings 260 | ASSERT_FALSE(WnbdResetAdapter()) << "couldn't reset WNBD adapter"; 261 | 262 | // Check the IO limits 263 | { 264 | MockWnbdDaemon WnbdDaemon(&WnbdProps); 265 | WnbdDaemon.Start(); 266 | 267 | std::string DiskPath = GetDiskPath(WnbdProps.InstanceName); 268 | HANDLE DiskHandle = CreateFileA( 269 | DiskPath.c_str(), 270 | GENERIC_WRITE, 271 | FILE_SHARE_READ, 272 | NULL, 273 | OPEN_EXISTING, 274 | FILE_ATTRIBUTE_NORMAL, 275 | NULL); 276 | ASSERT_NE(INVALID_HANDLE_VALUE, DiskHandle) 277 | << "couldn't open disk: " << DiskPath 278 | << ", error: " << WinStrError(GetLastError()); 279 | std::unique_ptr DiskHandleCloser( 280 | DiskHandle, &CloseHandle); 281 | 282 | Status = WnbdIoctlGetIOLimits( 283 | DiskHandle, 284 | &RetrievedLunIOLimit, 285 | &RetrievedAdapterIOLimit, 286 | nullptr); 287 | EXPECT_FALSE(Status) << "couldn't retrieve disk IO limits"; 288 | 289 | EXPECT_EQ(CustomLunIOLimit, RetrievedLunIOLimit); 290 | EXPECT_EQ(CustomAdapterIOLimit, RetrievedAdapterIOLimit); 291 | } 292 | 293 | // Reset IO limits 294 | Status = WnbdResetDrvOpt("MaxIOReqPerAdapter", TRUE); 295 | ASSERT_TRUE(!Status || Status == ERROR_FILE_NOT_FOUND) 296 | << "couldn't reset opt"; 297 | Status = WnbdResetDrvOpt("MaxIOReqPerLun", TRUE); 298 | ASSERT_TRUE(!Status || Status == ERROR_FILE_NOT_FOUND) 299 | << "couldn't reset opt"; 300 | 301 | // Reset the adapter in order to load the new settings 302 | // Adapter removals will be vetoed right after the disk is disconnected, 303 | // which is why we may need a few retries. 304 | EVENTUALLY(WnbdResetAdapter() == 0, 10, 500); 305 | 306 | // Check the IO limits 307 | { 308 | MockWnbdDaemon WnbdDaemon(&WnbdProps); 309 | WnbdDaemon.Start(); 310 | 311 | std::string DiskPath = GetDiskPath(WnbdProps.InstanceName); 312 | HANDLE DiskHandle = CreateFileA( 313 | DiskPath.c_str(), 314 | GENERIC_WRITE, 315 | FILE_SHARE_READ, 316 | NULL, 317 | OPEN_EXISTING, 318 | FILE_ATTRIBUTE_NORMAL, 319 | NULL); 320 | ASSERT_NE(INVALID_HANDLE_VALUE, DiskHandle) 321 | << "couldn't open disk: " << DiskPath 322 | << ", error: " << WinStrError(GetLastError()); 323 | std::unique_ptr DiskHandleCloser( 324 | DiskHandle, &CloseHandle); 325 | 326 | Status = WnbdIoctlGetIOLimits( 327 | DiskHandle, 328 | &RetrievedLunIOLimit, 329 | &RetrievedAdapterIOLimit, 330 | nullptr); 331 | EXPECT_FALSE(Status) << "couldn't retrieve disk IO limits"; 332 | 333 | EXPECT_EQ(WNBD_DEFAULT_MAX_IO_REQ_PER_LUN, RetrievedLunIOLimit); 334 | EXPECT_EQ(WNBD_DEFAULT_MAX_IO_REQ_PER_ADAPTER, RetrievedAdapterIOLimit); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/test_nbd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "pch.h" 8 | #include "mock_wnbd_daemon.h" 9 | #include "utils.h" 10 | #include "options.h" 11 | 12 | using namespace std; 13 | 14 | class NbdMapping { 15 | private: 16 | std::thread NbdDaemonThread; 17 | std::string InstanceName; 18 | 19 | public: 20 | NbdMapping(PWNBD_PROPERTIES WnbdProps) { 21 | string NbdExportName = GetOpt("nbd-export-name"); 22 | string NbdHostName = GetOpt("nbd-hostname"); 23 | DWORD NbdPort = GetOpt("nbd-port"); 24 | 25 | if (NbdExportName.empty()) { 26 | throw runtime_error("missing NBD export"); 27 | } 28 | if (NbdHostName.empty()) { 29 | throw runtime_error("missing NBD server address"); 30 | } 31 | 32 | // Fill the WNBD_PROPERTIES strucure 33 | InstanceName = GetNewInstanceName(); 34 | InstanceName.copy(WnbdProps->InstanceName, WNBD_MAX_NAME_LENGTH); 35 | string(WNBD_OWNER_NAME).copy(WnbdProps->Owner, WNBD_MAX_OWNER_LENGTH); 36 | 37 | WnbdProps->Flags.UseUserspaceNbd = 1; 38 | 39 | NbdHostName.copy(WnbdProps->NbdProperties.Hostname, 40 | WNBD_MAX_NAME_LENGTH); 41 | NbdExportName.copy(WnbdProps->NbdProperties.ExportName, 42 | WNBD_MAX_NAME_LENGTH); 43 | WnbdProps->NbdProperties.PortNumber = NbdPort; 44 | 45 | NbdDaemonThread = std::thread([&]{ 46 | WNBD_PROPERTIES Props = *WnbdProps; 47 | WnbdRunNbdDaemon(&Props); 48 | }); 49 | 50 | // Wait for the disk to become available. 51 | GetDiskPath(InstanceName.c_str(), false); 52 | } 53 | 54 | ~NbdMapping() { 55 | WNBD_REMOVE_OPTIONS RemoveOptions = { 0 }; 56 | RemoveOptions.Flags.HardRemove = 1; 57 | WnbdRemoveEx(InstanceName.c_str(), &RemoveOptions); 58 | 59 | if (NbdDaemonThread.joinable()) { 60 | cout << "Waiting for NBD daemon thread." << endl; 61 | NbdDaemonThread.join(); 62 | cout << "Nbd daemon stopped." << endl; 63 | } 64 | } 65 | }; 66 | 67 | bool CheckNbdParamsProvided() { 68 | // TODO: GTEST_SKIP was included in gtest 1.9, yet Nuget only provides 69 | // gtest 1.8. We should use GTEST_SKIP as soon as it becomes available. 70 | if (GetOpt("nbd-export-name").empty()) { 71 | cout << "No NBD export provided, skipping NBD tests." << endl; 72 | return false; 73 | } 74 | if (GetOpt("nbd-hostname").empty()) { 75 | cout << "No NBD server address provided, skipping NBD tests." << endl; 76 | return false; 77 | } 78 | return true; 79 | } 80 | 81 | TEST(TestNbd, TestMap) { 82 | if (!CheckNbdParamsProvided()) { 83 | return; 84 | } 85 | 86 | WNBD_PROPERTIES WnbdProps = { 0 }; 87 | WNBD_CONNECTION_INFO ConnectionInfo = { 0 }; 88 | 89 | { 90 | NbdMapping Mapping(&WnbdProps); 91 | 92 | NTSTATUS Status = WnbdShow(WnbdProps.InstanceName, &ConnectionInfo); 93 | ASSERT_FALSE(Status) << "couldn't retrieve WNBD disk info"; 94 | 95 | EXPECT_EQ( 96 | WnbdProps.InstanceName, 97 | string(ConnectionInfo.Properties.InstanceName)); 98 | EXPECT_EQ( 99 | WnbdProps.InstanceName, 100 | string(ConnectionInfo.Properties.SerialNumber)); 101 | EXPECT_EQ( 102 | string(WNBD_OWNER_NAME), 103 | string(ConnectionInfo.Properties.Owner)); 104 | EXPECT_LT(0UL, ConnectionInfo.Properties.BlockCount); 105 | EXPECT_LT(0ULL, ConnectionInfo.Properties.BlockSize); 106 | EXPECT_EQ(_getpid(), ConnectionInfo.Properties.Pid); 107 | 108 | EXPECT_TRUE(ConnectionInfo.Properties.Flags.UseUserspaceNbd); 109 | } 110 | 111 | // The mapping went out of scope, let's ensure that it got 112 | // disconnected. 113 | NTSTATUS Status = WnbdShow(WnbdProps.InstanceName, &ConnectionInfo); 114 | EXPECT_EQ(ERROR_FILE_NOT_FOUND, Status); 115 | } 116 | 117 | TEST(TestNbd, TestIO) { 118 | if (!CheckNbdParamsProvided()) { 119 | return; 120 | } 121 | 122 | // 1. Connect the NBD disk and retrieve connection information 123 | // ----------------------------------------------------------- 124 | WNBD_PROPERTIES WnbdProps = { 0 }; 125 | NbdMapping Mapping(&WnbdProps); 126 | 127 | WNBD_CONNECTION_INFO ConnectionInfo = { 0 }; 128 | NTSTATUS Status = WnbdShow(WnbdProps.InstanceName, &ConnectionInfo); 129 | ASSERT_FALSE(Status) << "couldn't retrieve WNBD disk info"; 130 | 131 | ASSERT_LT(0ULL, ConnectionInfo.Properties.BlockCount); 132 | EXPECT_LT(0UL, ConnectionInfo.Properties.BlockSize); 133 | 134 | UINT64 BlockCount = ConnectionInfo.Properties.BlockCount; 135 | UINT32 BlockSize = ConnectionInfo.Properties.BlockSize; 136 | 137 | string DiskPath = GetDiskPath(WnbdProps.InstanceName); 138 | DWORD OpenFlags = FILE_ATTRIBUTE_NORMAL | 139 | FILE_FLAG_NO_BUFFERING | 140 | FILE_FLAG_WRITE_THROUGH; 141 | HANDLE DiskHandle = CreateFileA( 142 | DiskPath.c_str(), 143 | GENERIC_READ | GENERIC_WRITE, 144 | FILE_SHARE_READ | FILE_SHARE_WRITE, 145 | NULL, 146 | OPEN_EXISTING, 147 | OpenFlags, 148 | NULL); 149 | ASSERT_NE(INVALID_HANDLE_VALUE, DiskHandle) 150 | << "couldn't open disk: " << DiskPath 151 | << ", error: " << WinStrError(GetLastError()); 152 | unique_ptr DiskHandleCloser( 153 | DiskHandle, &CloseHandle); 154 | 155 | // 2. Write one block at the beginning of the disk and then read it 156 | // ---------------------------------------------------------------- 157 | int WriteBufferSize = 4 << 20; 158 | static_assert(4 << 20 == 2 * WNBD_DEFAULT_MAX_TRANSFER_LENGTH); 159 | 160 | unique_ptr WriteBuffer( 161 | malloc(WriteBufferSize), free); 162 | ASSERT_TRUE(WriteBuffer.get()) << "couldn't allocate: " << WriteBufferSize; 163 | 164 | unsigned int Rand; 165 | EXPECT_EQ(0, rand_s(&Rand)); 166 | memset(WriteBuffer.get(), Rand, WriteBufferSize); 167 | 168 | int ReadBufferSize = 4 << 20; 169 | unique_ptr ReadBuffer( 170 | malloc(ReadBufferSize), free); 171 | ASSERT_TRUE(ReadBuffer.get()) << "couldn't allocate: " << ReadBufferSize; 172 | 173 | DWORD BytesWritten = 0; 174 | // 1 block 175 | ASSERT_TRUE(WriteFile( 176 | DiskHandle, WriteBuffer.get(), 177 | BlockSize, &BytesWritten, NULL)); 178 | ASSERT_EQ(BlockSize, BytesWritten); 179 | 180 | LARGE_INTEGER Offset = { 0 }; 181 | ASSERT_TRUE(SetFilePointerEx(DiskHandle, Offset, NULL, FILE_BEGIN)); 182 | 183 | DWORD BytesRead = 0; 184 | // Clear the read buffer 185 | memset(ReadBuffer.get(), 0, ReadBufferSize); 186 | ASSERT_TRUE(ReadFile( 187 | DiskHandle, ReadBuffer.get(), 188 | BlockSize, &BytesRead, NULL)); 189 | ASSERT_EQ(BlockSize, BytesRead); 190 | ASSERT_FALSE(memcmp(ReadBuffer.get(), WriteBuffer.get(), 191 | BytesRead)); 192 | 193 | // 3. Write twice the maximum transfer length and then verify the content 194 | // ---------------------------------------------------------------------- 195 | EXPECT_EQ(0, rand_s(&Rand)); 196 | memset(WriteBuffer.get(), Rand, WriteBufferSize); 197 | 198 | Offset.QuadPart = BlockSize; 199 | ASSERT_TRUE(SetFilePointerEx(DiskHandle, Offset, NULL, FILE_BEGIN)); 200 | 201 | ASSERT_TRUE(WriteFile( 202 | DiskHandle, WriteBuffer.get(), 203 | 2 * WNBD_DEFAULT_MAX_TRANSFER_LENGTH, 204 | &BytesWritten, NULL)); 205 | ASSERT_EQ(2 * WNBD_DEFAULT_MAX_TRANSFER_LENGTH, BytesWritten); 206 | 207 | Offset.QuadPart = BlockSize; 208 | ASSERT_TRUE(SetFilePointerEx(DiskHandle, Offset, NULL, FILE_BEGIN)); 209 | memset(ReadBuffer.get(), 0, ReadBufferSize); 210 | ASSERT_TRUE(ReadFile( 211 | DiskHandle, ReadBuffer.get(), 212 | 2 * WNBD_DEFAULT_MAX_TRANSFER_LENGTH, &BytesRead, NULL)); 213 | ASSERT_EQ(2 * WNBD_DEFAULT_MAX_TRANSFER_LENGTH, BytesRead); 214 | ASSERT_FALSE(memcmp(ReadBuffer.get(), WriteBuffer.get(), 215 | BytesRead)); 216 | 217 | // 4. Write one block at the end of the disk and verify the content 218 | // ---------------------------------------------------------------- 219 | EXPECT_EQ(0, rand_s(&Rand)); 220 | memset(WriteBuffer.get(), Rand, WriteBufferSize); 221 | 222 | Offset.QuadPart = (BlockCount - 1) * BlockSize; 223 | ASSERT_TRUE(SetFilePointerEx(DiskHandle, Offset, NULL, FILE_BEGIN)); 224 | ASSERT_TRUE(WriteFile( 225 | DiskHandle, WriteBuffer.get(), 226 | BlockSize, &BytesWritten, NULL)); 227 | ASSERT_EQ(BlockSize, BytesWritten); 228 | 229 | Offset.QuadPart = (BlockCount - 1) * BlockSize; 230 | ASSERT_TRUE(SetFilePointerEx(DiskHandle, Offset, NULL, FILE_BEGIN)); 231 | memset(ReadBuffer.get(), 0, ReadBufferSize); 232 | ASSERT_TRUE(ReadFile( 233 | DiskHandle, ReadBuffer.get(), 234 | BlockSize, &BytesRead, NULL)); 235 | ASSERT_EQ(BlockSize, BytesRead); 236 | ASSERT_FALSE(memcmp(ReadBuffer.get(), WriteBuffer.get(), 237 | BytesRead)); 238 | 239 | // 5. Read/Write past the end of the disk, expecting a failure. 240 | // -------------------------------------------------------------- 241 | Offset.QuadPart = BlockCount * BlockSize; 242 | ASSERT_TRUE(SetFilePointerEx(DiskHandle, Offset, NULL, FILE_BEGIN)); 243 | 244 | ASSERT_FALSE(ReadFile( 245 | DiskHandle, ReadBuffer.get(), 246 | BlockSize, &BytesRead, NULL)); 247 | ASSERT_EQ(0, BytesRead); 248 | 249 | ASSERT_FALSE(WriteFile( 250 | DiskHandle, WriteBuffer.get(), 251 | BlockSize, &BytesWritten, NULL)); 252 | ASSERT_EQ(0, BytesWritten); 253 | 254 | ASSERT_TRUE(FlushFileBuffers(DiskHandle)); 255 | } 256 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "pch.h" 8 | 9 | #include "utils.h" 10 | 11 | std::string GetNewInstanceName() 12 | { 13 | auto TestInstance = ::testing::UnitTest::GetInstance(); 14 | auto TestName = std::string(TestInstance->current_test_info()->name()); 15 | 16 | unsigned int Rand; 17 | rand_s(&Rand); 18 | return TestName + "-" + std::to_string(Rand); 19 | } 20 | 21 | std::string WinStrError(DWORD Err) 22 | { 23 | LPSTR Msg = NULL; 24 | DWORD MsgLen = ::FormatMessageA( 25 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 26 | FORMAT_MESSAGE_FROM_SYSTEM | 27 | FORMAT_MESSAGE_IGNORE_INSERTS, 28 | NULL, 29 | Err, 30 | 0, 31 | (LPSTR)&Msg, 32 | 0, 33 | NULL); 34 | 35 | std::ostringstream MsgStream; 36 | MsgStream << "(" << Err << ") "; 37 | if (!MsgLen) { 38 | MsgStream << "Unknown error"; 39 | } 40 | else { 41 | MsgStream << Msg; 42 | ::LocalFree(Msg); 43 | } 44 | return MsgStream.str(); 45 | } 46 | 47 | // Retrieves the disk path and waits for it to become available. 48 | std::string GetDiskPath(const char* InstanceName, bool ExpectMapped) 49 | { 50 | DWORD TimeoutMs = 10 * 1000; 51 | DWORD RetryIntervalMs = 500; 52 | 53 | DWORD DiskNumber; 54 | DWORD Status = WnbdPollDiskNumber( 55 | InstanceName, ExpectMapped, 56 | TRUE, // TryOpen 57 | TimeoutMs, RetryIntervalMs, 58 | &DiskNumber); 59 | if (Status) { 60 | std::string Msg = "Couldn't retrieve disk number, error: " + 61 | std::to_string(Status); 62 | throw std::runtime_error(Msg); 63 | } 64 | 65 | return "\\\\.\\PhysicalDrive" + std::to_string(DiskNumber); 66 | } 67 | 68 | void SetDiskWritable(std::string InstanceName) 69 | { 70 | std::string DiskPath = GetDiskPath(InstanceName.c_str()); 71 | 72 | HANDLE DiskHandle = CreateFileA( 73 | DiskPath.c_str(), 74 | GENERIC_READ | GENERIC_WRITE, 75 | FILE_SHARE_READ | FILE_SHARE_WRITE, 76 | NULL, 77 | OPEN_EXISTING, 78 | NULL, 79 | NULL); 80 | if (DiskHandle == INVALID_HANDLE_VALUE) { 81 | std::string Msg = "couldn't open wnbd disk: " + InstanceName + 82 | ", error: " + WinStrError(GetLastError()); 83 | throw std::runtime_error(Msg); 84 | } 85 | std::unique_ptr DiskHandleCloser( 86 | DiskHandle, &CloseHandle); 87 | 88 | SetDiskWritable(DiskHandle); 89 | } 90 | 91 | void SetDiskWritable(HANDLE DiskHandle) 92 | { 93 | DWORD BytesReturned = 0; 94 | 95 | SET_DISK_ATTRIBUTES attributes = {0}; 96 | attributes.Version = sizeof(attributes); 97 | attributes.Attributes = 0; // clear read-only flag 98 | attributes.AttributesMask = DISK_ATTRIBUTE_READ_ONLY; 99 | 100 | BOOL Succeeded = DeviceIoControl( 101 | DiskHandle, 102 | IOCTL_DISK_SET_DISK_ATTRIBUTES, 103 | (LPVOID) &attributes, 104 | (DWORD) sizeof(attributes), 105 | NULL, 106 | 0, 107 | &BytesReturned, 108 | NULL); 109 | if (!Succeeded) { 110 | std::string Msg = "couldn't set wnbd disk as writable, error: " + 111 | WinStrError(GetLastError()); 112 | throw std::runtime_error(Msg); 113 | } 114 | } 115 | 116 | std::string GetEnv(std::string Name) 117 | { 118 | char* ValBuff; 119 | size_t ReqSize; 120 | 121 | // VS throws warnings when using getenv, which it considers unsafe. 122 | errno_t Err = getenv_s(&ReqSize, NULL, 0, Name.c_str()); 123 | if (Err && Err != ERANGE) { 124 | std::string Msg = ( 125 | "couldn't retrieve env var: " + Name + 126 | ". Error: " + std::to_string(Err)); 127 | } 128 | if (!ReqSize) { 129 | return ""; 130 | } 131 | 132 | ValBuff = (char*) malloc(ReqSize); 133 | if (!ValBuff) { 134 | std::string Msg = "couldn't allocate: " + std::to_string(ReqSize); 135 | throw std::runtime_error(Msg); 136 | } 137 | 138 | Err = getenv_s(&ReqSize, ValBuff, ReqSize, Name.c_str()); 139 | if (Err) { 140 | free(ValBuff); 141 | 142 | std::string Msg = ( 143 | "couldn't retrieve env var: " + Name + 144 | ". Error: " + std::to_string(Err)); 145 | throw std::runtime_error(Msg); 146 | } 147 | 148 | std::string ValStr = std::string(ValBuff, ReqSize); 149 | free(ValBuff); 150 | return ValStr; 151 | } 152 | 153 | std::string ByteArrayToHex(BYTE* arr, int length) 154 | { 155 | std::stringstream ss; 156 | 157 | for (int i = 0; i < length; i++) 158 | ss << std::hex << (int) arr[i] << " "; 159 | 160 | return ss.str(); 161 | } 162 | 163 | DWORD WnbdOptionList::Retrieve(BOOLEAN Persistent) 164 | { 165 | DWORD ReqBuffSz = 0; 166 | DWORD Status = 0; 167 | 168 | if (OptionList && BuffSz) { 169 | memset(OptionList, 0, BuffSz); 170 | } 171 | 172 | do { 173 | if (ReqBuffSz) { 174 | if (OptionList) { 175 | free(OptionList); 176 | } 177 | 178 | OptionList = (PWNBD_OPTION_LIST) calloc(1, ReqBuffSz); 179 | if (!OptionList) { 180 | Status = ERROR_NOT_ENOUGH_MEMORY; 181 | break; 182 | } 183 | BuffSz = ReqBuffSz; 184 | } else { 185 | ReqBuffSz = BuffSz; 186 | } 187 | 188 | // If the buffer is too small, the return value is 0 and "ReqBuffSz" 189 | // will contain the required size. 190 | Status = WnbdListDrvOpt(OptionList, &ReqBuffSz, Persistent); 191 | if (Status) 192 | break; 193 | } while (BuffSz < ReqBuffSz); 194 | 195 | if (Status && OptionList) { 196 | free(OptionList); 197 | OptionList = nullptr; 198 | } 199 | 200 | return Status; 201 | } 202 | 203 | PWNBD_OPTION WnbdOptionList::GetOpt(PCWSTR Name) 204 | { 205 | PWNBD_OPTION Option = nullptr; 206 | 207 | if (!OptionList) { 208 | return nullptr; 209 | } 210 | 211 | for (unsigned int Idx=0; Idx < OptionList->Count; Idx++) { 212 | if (!wcscmp(Name, OptionList->Options[Idx].Name)) { 213 | Option = &OptionList->Options[Idx]; 214 | break; 215 | } 216 | } 217 | 218 | return Option; 219 | } 220 | 221 | DWORD WnbdConnectionList::Retrieve() 222 | { 223 | DWORD ReqBuffSz = 0; 224 | DWORD Status = 0; 225 | 226 | if (ConnList && BuffSz) { 227 | memset(ConnList, 0, BuffSz); 228 | } 229 | 230 | do { 231 | if (ReqBuffSz) { 232 | if (ConnList) { 233 | free(ConnList); 234 | } 235 | 236 | ConnList = (PWNBD_CONNECTION_LIST) calloc(1, ReqBuffSz); 237 | if (!ConnList) { 238 | Status = ERROR_NOT_ENOUGH_MEMORY; 239 | break; 240 | } 241 | BuffSz = ReqBuffSz; 242 | } else { 243 | ReqBuffSz = BuffSz; 244 | } 245 | 246 | // If the buffer is too small, the return value is 0 and "ReqBuffSz" 247 | // will contain the required size. 248 | Status = WnbdList(ConnList, &ReqBuffSz); 249 | if (Status) 250 | break; 251 | } while (BuffSz < ReqBuffSz); 252 | 253 | if (Status && ConnList) { 254 | free(ConnList); 255 | ConnList = nullptr; 256 | } 257 | 258 | return Status; 259 | } 260 | 261 | PWNBD_CONNECTION_INFO WnbdConnectionList::GetConn(PCSTR InstanceName) 262 | { 263 | PWNBD_CONNECTION_INFO Conn = nullptr; 264 | 265 | if (!ConnList) { 266 | return nullptr; 267 | } 268 | 269 | for (unsigned int Idx=0; Idx < ConnList->Count; Idx++) { 270 | if (!strcmp(InstanceName, 271 | ConnList->Connections[Idx].Properties.InstanceName)) { 272 | Conn = &ConnList->Connections[Idx]; 273 | break; 274 | } 275 | } 276 | 277 | return Conn; 278 | } 279 | 280 | void GetNewWnbdProps(PWNBD_PROPERTIES WnbdProps) { 281 | auto InstanceName = GetNewInstanceName(); 282 | InstanceName.copy(WnbdProps->InstanceName, sizeof(WnbdProps->InstanceName)); 283 | strncpy_s( 284 | WnbdProps->Owner, WNBD_MAX_OWNER_LENGTH, 285 | WNBD_OWNER_NAME, strlen(WNBD_OWNER_NAME)); 286 | 287 | WnbdProps->BlockCount = DefaultBlockCount; 288 | WnbdProps->BlockSize = DefaultBlockSize; 289 | WnbdProps->MaxUnmapDescCount = 1; 290 | WnbdProps->Flags.UnmapSupported = 1; 291 | } 292 | 293 | int InitializeWinsock() { 294 | WSADATA WsaData; 295 | 296 | int Ret = WSAStartup(MAKEWORD(2, 2), &WsaData); 297 | if (Ret) { 298 | auto Err = WSAGetLastError(); 299 | std::cerr << "WSAStartup failed. " 300 | << "Error code: " << Err 301 | << ". Error message: " << WinStrError(Err) 302 | << std::endl; 303 | } 304 | return Ret; 305 | } 306 | -------------------------------------------------------------------------------- /tests/libwnbd_tests/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Cloudbase Solutions 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | 13 | #define EVENTUALLY(expression, retry_attempts, retry_interval_ms) \ 14 | { \ 15 | static_assert(retry_attempts > 0); \ 16 | static_assert(retry_interval_ms > 0); \ 17 | int _retry_attempts = retry_attempts; \ 18 | bool ok = false; \ 19 | while (_retry_attempts--) { \ 20 | if (expression) { \ 21 | ok = true; \ 22 | break; \ 23 | } else { \ 24 | Sleep(retry_interval_ms); \ 25 | } \ 26 | } \ 27 | if (!ok) \ 28 | GTEST_FATAL_FAILURE_("Expression mismatch: "#expression); \ 29 | } 30 | 31 | #define WNBD_OWNER_NAME "WnbdTests" 32 | 33 | static const uint64_t DefaultBlockCount = 1 << 20; 34 | static const uint64_t DefaultBlockSize = 512; 35 | 36 | std::string GetNewInstanceName(); 37 | 38 | // Converts a Windows error code to a string, including the error 39 | // description. 40 | std::string WinStrError(DWORD Err); 41 | int InitializeWinsock(); 42 | 43 | // Retrieves the disk path and waits for it to become available. 44 | // Raises a runtime error upon failure. 45 | std::string GetDiskPath(const char* InstanceName, bool ExpectMapped=true); 46 | 47 | // Configures the specified disk as writable. 48 | // Raises a runtime error upon failure. 49 | void SetDiskWritable(std::string InstanceName); 50 | void SetDiskWritable(HANDLE DiskHandle); 51 | 52 | // Fetches the specified env variable, returning an empty string if 53 | // missing. 54 | // Raises a runtime error upon failure. 55 | std::string GetEnv(std::string Name); 56 | 57 | // Returns a string containing the hex values of the byte array 58 | // received as parameter. 59 | std::string ByteArrayToHex(BYTE* arr, int length); 60 | 61 | // A simple wrapper on top of WNBD_OPTION_LIST, making it easier 62 | // to retrieve and parse. 63 | class WnbdOptionList { 64 | private: 65 | PWNBD_OPTION_LIST OptionList = nullptr; 66 | DWORD BuffSz = 0; 67 | 68 | public: 69 | ~WnbdOptionList() { 70 | if (OptionList) { 71 | free(OptionList); 72 | OptionList = nullptr; 73 | } 74 | } 75 | 76 | DWORD Retrieve(BOOLEAN Persistent); 77 | PWNBD_OPTION GetOpt(PCWSTR Name); 78 | }; 79 | 80 | // A simple wrapper on top of WNBD_CONNECTION_LIST, making it easier 81 | // to retrieve and parse. 82 | class WnbdConnectionList { 83 | private: 84 | PWNBD_CONNECTION_LIST ConnList = nullptr; 85 | DWORD BuffSz = 0; 86 | 87 | public: 88 | ~WnbdConnectionList() { 89 | if (ConnList) { 90 | free(ConnList); 91 | ConnList = nullptr; 92 | } 93 | } 94 | 95 | DWORD Retrieve(); 96 | PWNBD_CONNECTION_INFO GetConn(PCSTR InstanceName); 97 | }; 98 | 99 | void GetNewWnbdProps(PWNBD_PROPERTIES); 100 | -------------------------------------------------------------------------------- /vstudio/build_deps.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | Analyze 10 | x64 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {4ee28b36-2083-4fba-88d7-fe60027b81bb} 25 | builddeps 26 | 10.0 27 | 28 | 29 | 30 | Utility 31 | v142 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Missing dependency: {0}. It was supposed to be retrieved using "get_dependencies.ps1". 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /vstudio/driver.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Analyze 6 | x64 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | x64 15 | 16 | 17 | 18 | {0091FAE6-479E-40CB-985E-1AC3FB633391} 19 | {dd38f7fc-d7bd-488b-9242-7d8754cde80d} 20 | v4.5 21 | 12.0 22 | Debug 23 | x64 24 | wnbd 25 | $(LatestTargetPlatformVersion) 26 | 27 | 28 | 29 | Windows10 30 | true 31 | WindowsKernelModeDriver10.0 32 | Driver 33 | WDM 34 | 35 | 36 | Windows10 37 | false 38 | WindowsKernelModeDriver10.0 39 | Driver 40 | WDM 41 | 42 | 43 | Windows10 44 | false 45 | WindowsKernelModeDriver10.0 46 | Driver 47 | WDM 48 | Yes 49 | 50 | 51 | 52 | 53 | true 54 | .\$(IntDir) 55 | true 56 | "$(SDK_INC_PATH)\winmeta.xml" 57 | .\$(IntDir) 58 | true 59 | events 60 | true 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | DbgengKernelDebugger 72 | wnbd 73 | $(CRT_IncludePath);$(KM_IncludePath);$(KIT_SHARED_IncludePath);$(IncludePath);$(IntDir) 74 | true 75 | $(Platform)\$(ConfigurationName)\obj\ 76 | 77 | 78 | DbgengKernelDebugger 79 | wnbd 80 | $(CRT_IncludePath);$(KM_IncludePath);$(KIT_SHARED_IncludePath);$(IncludePath);$(IntDir) 81 | true 82 | $(Platform)\$(ConfigurationName)\obj\ 83 | 84 | 85 | DbgengKernelDebugger 86 | wnbd 87 | $(CRT_IncludePath);$(KM_IncludePath);$(KIT_SHARED_IncludePath);$(IncludePath);$(IntDir) 88 | true 89 | $(Platform)\$(ConfigurationName)\obj\ 90 | true 91 | 92 | 93 | 94 | ..\include;$(IntDir);%(AdditionalIncludeDirectories) 95 | true 96 | WPP_INLINE __inline 97 | WPPFILE="%(Filename).tmh";_WIN64;_AMD64_;AMD64;POOL_ZERO_DOWN_LEVEL_SUPPORT;%(PreprocessorDefinitions) 98 | WnbdWppTrace{FLAGS=MYDRIVER_ALL_INFO}(LEVEL,MSG,...) 99 | $(KMDF_INC_PATH)$(KMDF_VER_PATH)\WdfTraceEnums.h 100 | 101 | 102 | 103 | 104 | $(DDK_LIB_PATH )libcntpr.lib;$(DDK_LIB_PATH )wdmsec.lib;$(DDK_LIB_PATH )netio.lib;$(DDK_LIB_PATH )storport.lib;%(AdditionalDependencies) 105 | $(OutDir)\pdb\$(ProjectName)\$(TargetName).pdb 106 | 107 | 108 | SHA256 109 | 110 | 111 | false 112 | 113 | 114 | false 115 | 116 | 117 | 118 | 119 | ..\include;$(IntDir);%(AdditionalIncludeDirectories) 120 | true 121 | WPP_INLINE __inline 122 | WPPFILE="%(Filename).tmh";_WIN64;_AMD64_;AMD64;POOL_ZERO_DOWN_LEVEL_SUPPORT;%(PreprocessorDefinitions) 123 | WnbdWppTrace{FLAGS=MYDRIVER_ALL_INFO}(LEVEL,MSG,...) 124 | $(KMDF_INC_PATH)$(KMDF_VER_PATH)\WdfTraceEnums.h 125 | 126 | 127 | libcntpr.lib;wdmsec.lib;$(DDK_LIB_PATH )netio.lib;$(DDK_LIB_PATH )storport.lib;%(AdditionalDependencies) 128 | $(OutDir)\pdb\$(ProjectName)\$(TargetName).pdb 129 | 130 | 131 | SHA256 132 | 133 | 134 | false 135 | 136 | 137 | false 138 | 139 | 140 | 141 | 142 | ..\include;$(IntDir);%(AdditionalIncludeDirectories) 143 | _WIN64;_AMD64_;AMD64;POOL_ZERO_DOWN_LEVEL_SUPPORT;%(PreprocessorDefinitions) 144 | 145 | 146 | libcntpr.lib;wdmsec.lib;$(DDK_LIB_PATH )netio.lib;$(DDK_LIB_PATH )storport.lib;%(AdditionalDependencies) 147 | $(OutDir)\pdb\$(ProjectName)\$(TargetName).pdb 148 | 149 | 150 | SHA256 151 | 152 | 153 | 154 | powershell.exe -executionpolicy bypass -command $env:VCINSTALLDIR='$(VCInstallDir)'; cd $(SolutionDir); msbuild $(ProjectFileName) /t:sdv /p:inputs='/check' /p:Configuration=Debug /p:SolutionDir=$(SolutionDir); 155 | 156 | 157 | 158 | false 159 | 160 | 161 | false 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /vstudio/generate_version_h.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | $scriptLocation = [System.IO.Path]::GetDirectoryName( 4 | $myInvocation.MyCommand.Definition) 5 | 6 | $versionHeaderPath = "$scriptLocation/../include/version.h" 7 | $driverInfBasePath = "$scriptLocation/../driver/wnbd.inf" 8 | # The wnbd.inf file is updated automatically (e.g. including the version tag). In order 9 | # to avoid unintended git changes, we'll make a copy. 10 | $driverInfDestPath = "$scriptLocation/../vstudio/wnbd.inf" 11 | 12 | function safe_exec() { 13 | # Powershell doesn't check the command exit code, we'll need to 14 | # do it ourselves. Also, in case of native commands, it treats stderr 15 | # output as an exception, which is why we'll have to capture it. 16 | cmd /c "$args 2>&1" 17 | if ($LASTEXITCODE) { 18 | throw "Command failed: $args" 19 | } 20 | } 21 | 22 | try { 23 | $gitShortDesc = safe_exec git describe --tags 24 | $gitLongDesc = safe_exec git describe --tags --long 25 | $gitTag = safe_exec git describe --tags --abbrev=0 26 | $gitBranch = safe_exec git branch --show-current 27 | $gitCommitCount = safe_exec git rev-list --count "$gitTag..$gitBranch" 28 | $isDev = (($gitLongDesc) -split "-")[-2] -ne "0" 29 | 30 | $gitTag -match "(?\d+)\.(?\d+)\.(?\d+)" 31 | if ($Matches.Count -ne 4) { 32 | throw "Invalid version tag: $gitTag. Expecting a semantic version, such as '1.0.0-beta'." 33 | } 34 | 35 | $versionMajor = $Matches.major 36 | $versionMinor = $Matches.minor 37 | $versionPatch = $Matches.patch 38 | $versionStr = "$gitShortDesc" 39 | $versionStrMS = "$versionMajor.$versionMinor.$versionPatch.$gitCommitCount" 40 | $versionDetected = $true 41 | } 42 | catch { 43 | $errMsg = [string]$_.Exception.Message 44 | $warnMsg = "Could not detect WNBD version using the git tag. The following header " + 45 | "will have to be updated manually: " 46 | Write-Warning $warnMsg 47 | # Visual Studio is truncating long messages, we'll use a separate warning 48 | # for the actual path... 49 | Write-Warning $versionHeaderPath 50 | # Even though it's just a warning, we have to avoid using "Error:". 51 | # Visual Studio parses the message and will treat it as an error, saying that 52 | # this script returned -1, even if it didn't... 53 | Write-Warning "Original exception: $errMsg" 54 | 55 | $versionMajor = "X" 56 | $versionMinor = "Y" 57 | $versionPatch = "Z" 58 | $versionStr = "X.Y.Z" 59 | $versionStrMS = "X.Y.Z.0" 60 | $gitCommitCount = 0 61 | } 62 | 63 | # We might add some more info to the version string. 64 | $versionStrMaxLen = 127 65 | $versionStrLen = $versionStr.Length 66 | if ($versionStrLen -gt 127) { 67 | throw "Version string too large. Length: $versionStrLen, maximum length: $versionStrMaxLen." 68 | } 69 | 70 | $versionHeader = @" 71 | // Automatically generated using generate_version_h.ps1 at build time. 72 | 73 | #pragma once 74 | 75 | #define WNBD_VERSION_MAJOR ${versionMajor} 76 | #define WNBD_VERSION_MINOR ${versionMinor} 77 | #define WNBD_VERSION_PATCH ${versionPatch} 78 | #define WNBD_COMMIT_COUNT ${gitCommitCount} 79 | 80 | #define WNBD_VERSION_STR "${versionStr}" 81 | #define WNBD_VERSION_STR_MS "${versionStrMS}" 82 | "@ 83 | 84 | # If we can't detect the project version using the git tag, we're providing 85 | # a template that the user can fill, which we won't overwrite. 86 | if ($versionDetected -or (!(test-path $versionHeaderPath))) { 87 | echo $versionHeader | out-file -encoding utf8 -filepath $versionHeaderPath 88 | if (!($versionDetected)) { 89 | $err = @" 90 | #error The WNBD version could not be automatically detected using the git tag. \ 91 | Please fill in the WNBD version and then remove this error. 92 | "@ 93 | echo $err | out-file -append -encoding utf8 -filepath $versionHeaderPath 94 | } 95 | } 96 | 97 | # update the driver inf version 98 | cp $driverInfBasePath $driverInfDestPath 99 | if ($versionDetected) { 100 | $infVersion = "${versionMajor}.${versionMinor}.${versionPatch}.${gitCommitCount}" 101 | safe_exec stampinf.exe -d "*" -a "amd64" -v "$infVersion" -f "$driverInfDestPath" 102 | } else { 103 | safe_exec stampinf.exe -d "*" -a "amd64" -v "*" -f "$driverInfDestPath" 104 | } 105 | -------------------------------------------------------------------------------- /vstudio/reinstall.ps1: -------------------------------------------------------------------------------- 1 | $scriptLocation = [System.IO.Path]::GetDirectoryName( 2 | $myInvocation.MyCommand.Definition) 3 | 4 | $wnbdBin = "$scriptLocation\wnbd-client.exe" 5 | $wnbdInf = "$scriptLocation\wnbd.inf" 6 | $wnbdCat = "$scriptLocation\wnbd.cat" 7 | $wnbdSys = "$scriptLocation\wnbd.sys" 8 | $wnbdEvents = "$scriptLocation\wnbdevents.xml" 9 | 10 | $requiredFiles = @($wnbdBin, $wnbdInf, $wnbdCat, $wnbdSys) 11 | foreach ($path in $requiredFiles) { 12 | if (!(Test-Path -Path $path -PathType leaf)) { 13 | Write-Warning "Could not find file: $path" 14 | } 15 | } 16 | 17 | wevtutil um $wnbdEvents 18 | & $wnbdBin uninstall-driver 19 | 20 | & $wnbdBin install-driver $wnbdInf 21 | wevtutil im $wnbdEvents 22 | -------------------------------------------------------------------------------- /vstudio/wnbd.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.29709.97 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "driver", "driver.vcxproj", "{0091FAE6-479E-40CB-985E-1AC3FB633391}" 6 | ProjectSection(ProjectDependencies) = postProject 7 | {4EE28B36-2083-4FBA-88D7-FE60027B81BB} = {4EE28B36-2083-4FBA-88D7-FE60027B81BB} 8 | EndProjectSection 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wnbd-client", "..\wnbd-client\wnbd-client.vcxproj", "{A9A19AD0-D8F6-4D41-9B82-375370C408D7}" 11 | ProjectSection(ProjectDependencies) = postProject 12 | {15FFEE2F-F65A-47D1-8389-15E5ADD971CA} = {15FFEE2F-F65A-47D1-8389-15E5ADD971CA} 13 | {4EE28B36-2083-4FBA-88D7-FE60027B81BB} = {4EE28B36-2083-4FBA-88D7-FE60027B81BB} 14 | EndProjectSection 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libwnbd", "..\libwnbd\libwnbd.vcxproj", "{15FFEE2F-F65A-47D1-8389-15E5ADD971CA}" 17 | ProjectSection(ProjectDependencies) = postProject 18 | {4EE28B36-2083-4FBA-88D7-FE60027B81BB} = {4EE28B36-2083-4FBA-88D7-FE60027B81BB} 19 | EndProjectSection 20 | EndProject 21 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "build_deps", "build_deps.vcxproj", "{4EE28B36-2083-4FBA-88D7-FE60027B81BB}" 22 | EndProject 23 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libwnbd_tests", "..\tests\libwnbd_tests\libwnbd_tests.vcxproj", "{CB9F16AC-7578-4122-A364-A54AA527C426}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Analyze|x64 = Analyze|x64 28 | Debug|x64 = Debug|x64 29 | Release|x64 = Release|x64 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {0091FAE6-479E-40CB-985E-1AC3FB633391}.Analyze|x64.ActiveCfg = Analyze|x64 33 | {0091FAE6-479E-40CB-985E-1AC3FB633391}.Analyze|x64.Build.0 = Analyze|x64 34 | {0091FAE6-479E-40CB-985E-1AC3FB633391}.Debug|x64.ActiveCfg = Debug|x64 35 | {0091FAE6-479E-40CB-985E-1AC3FB633391}.Debug|x64.Build.0 = Debug|x64 36 | {0091FAE6-479E-40CB-985E-1AC3FB633391}.Debug|x64.Deploy.0 = Debug|x64 37 | {0091FAE6-479E-40CB-985E-1AC3FB633391}.Release|x64.ActiveCfg = Release|x64 38 | {0091FAE6-479E-40CB-985E-1AC3FB633391}.Release|x64.Build.0 = Release|x64 39 | {0091FAE6-479E-40CB-985E-1AC3FB633391}.Release|x64.Deploy.0 = Release|x64 40 | {A9A19AD0-D8F6-4D41-9B82-375370C408D7}.Analyze|x64.ActiveCfg = Analyze|x64 41 | {A9A19AD0-D8F6-4D41-9B82-375370C408D7}.Analyze|x64.Build.0 = Analyze|x64 42 | {A9A19AD0-D8F6-4D41-9B82-375370C408D7}.Debug|x64.ActiveCfg = Debug|x64 43 | {A9A19AD0-D8F6-4D41-9B82-375370C408D7}.Debug|x64.Build.0 = Debug|x64 44 | {A9A19AD0-D8F6-4D41-9B82-375370C408D7}.Release|x64.ActiveCfg = Release|x64 45 | {A9A19AD0-D8F6-4D41-9B82-375370C408D7}.Release|x64.Build.0 = Release|x64 46 | {15FFEE2F-F65A-47D1-8389-15E5ADD971CA}.Analyze|x64.ActiveCfg = Analyze|x64 47 | {15FFEE2F-F65A-47D1-8389-15E5ADD971CA}.Analyze|x64.Build.0 = Analyze|x64 48 | {15FFEE2F-F65A-47D1-8389-15E5ADD971CA}.Debug|x64.ActiveCfg = Debug|x64 49 | {15FFEE2F-F65A-47D1-8389-15E5ADD971CA}.Debug|x64.Build.0 = Debug|x64 50 | {15FFEE2F-F65A-47D1-8389-15E5ADD971CA}.Release|x64.ActiveCfg = Release|x64 51 | {15FFEE2F-F65A-47D1-8389-15E5ADD971CA}.Release|x64.Build.0 = Release|x64 52 | {4EE28B36-2083-4FBA-88D7-FE60027B81BB}.Analyze|x64.ActiveCfg = Analyze|x64 53 | {4EE28B36-2083-4FBA-88D7-FE60027B81BB}.Analyze|x64.Build.0 = Analyze|x64 54 | {4EE28B36-2083-4FBA-88D7-FE60027B81BB}.Debug|x64.ActiveCfg = Debug|x64 55 | {4EE28B36-2083-4FBA-88D7-FE60027B81BB}.Debug|x64.Build.0 = Debug|x64 56 | {4EE28B36-2083-4FBA-88D7-FE60027B81BB}.Release|x64.ActiveCfg = Release|x64 57 | {4EE28B36-2083-4FBA-88D7-FE60027B81BB}.Release|x64.Build.0 = Release|x64 58 | {CB9F16AC-7578-4122-A364-A54AA527C426}.Analyze|x64.ActiveCfg = Debug|x64 59 | {CB9F16AC-7578-4122-A364-A54AA527C426}.Debug|x64.ActiveCfg = Debug|x64 60 | {CB9F16AC-7578-4122-A364-A54AA527C426}.Debug|x64.Build.0 = Debug|x64 61 | {CB9F16AC-7578-4122-A364-A54AA527C426}.Release|x64.ActiveCfg = Release|x64 62 | {CB9F16AC-7578-4122-A364-A54AA527C426}.Release|x64.Build.0 = Release|x64 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | GlobalSection(ExtensibilityGlobals) = postSolution 68 | SolutionGuid = {AF477CB1-DB6A-40EF-9E01-934831548150} 69 | EndGlobalSection 70 | EndGlobal 71 | -------------------------------------------------------------------------------- /vstudio/wnbdevents.xml: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 10 | 16 | 17 | 20 | 21 | 22 | 33 | 34 | 35 | 43 | 51 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /wnbd-client/client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | typedef void (*GetOptionsFunc)( 16 | boost::program_options::positional_options_description &positonal_opts, 17 | boost::program_options::options_description &named_opts); 18 | typedef DWORD (*ExecuteFunc)(const boost::program_options::variables_map &vm); 19 | 20 | class Client { 21 | public: 22 | struct Command { 23 | std::string name; 24 | std::vector aliases; 25 | std::string description; 26 | 27 | ExecuteFunc execute = nullptr; 28 | GetOptionsFunc get_options = nullptr; 29 | 30 | Command(std::string _name, 31 | std::vector _aliases, 32 | std::string _description, 33 | ExecuteFunc _execute, 34 | GetOptionsFunc _get_options = nullptr) 35 | : name(_name) 36 | , aliases(_aliases) 37 | , description(_description) 38 | , execute(_execute) 39 | , get_options(_get_options) 40 | { 41 | Client::commands.push_back(this); 42 | } 43 | }; 44 | 45 | static std::vector commands; 46 | static const size_t LINE_WIDTH = 80; 47 | static const size_t MIN_LCOLUMN_WIDTH = 20; 48 | 49 | DWORD execute(int argc, const char** argv); 50 | static Command* get_command(std::string name); 51 | 52 | static void get_common_options(boost::program_options::options_description& options); 53 | }; 54 | -------------------------------------------------------------------------------- /wnbd-client/cmd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | 24 | #define WNBD_CLI_OWNER_NAME "wnbd-client" 25 | 26 | void 27 | PrintSyntax(); 28 | 29 | DWORD 30 | CmdUnmap( 31 | std::string InstanceName, 32 | BOOLEAN HardRemove, 33 | BOOLEAN NoHardDisonnectFallback, 34 | DWORD SoftDisconnectTimeout, 35 | DWORD SoftDisconnectRetryInterval); 36 | 37 | DWORD 38 | CmdStats(std::string InstanceName); 39 | 40 | DWORD 41 | CmdMap( 42 | std::string InstanceName, 43 | std::string HostName, 44 | DWORD PortNumber, 45 | std::string ExportName, 46 | UINT64 DiskSize, 47 | UINT32 BlockSize, 48 | BOOLEAN MustNegotiate, 49 | BOOLEAN ReadOnly); 50 | 51 | DWORD 52 | CmdList(); 53 | 54 | DWORD 55 | CmdShow(std::string InstanceName); 56 | 57 | DWORD 58 | CmdVersion(); 59 | 60 | DWORD 61 | CmdGetOpt(std::string Name, BOOLEAN Persistent); 62 | 63 | DWORD 64 | CmdSetOpt(std::string Name, std::string Value, BOOLEAN Persistent); 65 | 66 | DWORD 67 | CmdResetOpt(std::string Name, BOOLEAN Persistent); 68 | 69 | DWORD 70 | CmdListOpt(BOOLEAN Persistent); 71 | 72 | DWORD 73 | CmdUninstall(); 74 | 75 | DWORD 76 | CmdInstall(std::string FileName); 77 | 78 | DWORD 79 | CmdResetAdapter( 80 | BOOLEAN HardRemoveMappings, 81 | DWORD ResetTimeout, 82 | DWORD ResetRetryInterval); 83 | -------------------------------------------------------------------------------- /wnbd-client/code_page.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UTF-8 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /wnbd-client/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "client.h" 8 | 9 | int main(int argc, const char** argv) 10 | { 11 | SetConsoleOutputCP(CP_UTF8); 12 | return Client().execute(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /wnbd-client/usage.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #include "client.h" 8 | #include "usage.h" 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | namespace po = boost::program_options; 17 | using namespace std; 18 | 19 | string fmt_text(size_t margin, size_t width, string text) 20 | { 21 | string result; 22 | boost::trim(text); 23 | while (!text.empty()) 24 | { 25 | // Don't apply padding to the first line. 26 | if (!result.empty()) { 27 | result += string(margin, ' '); 28 | } 29 | 30 | // The text size exceeds the line width so we're trying to find 31 | // the best place to break the line. 32 | size_t n = text.size(); 33 | if (text.size() > width) { 34 | n = text.rfind(" ", width); 35 | if (n == string::npos) { 36 | n = text.find(" "); 37 | } 38 | if (n == string::npos) { 39 | n = text.length(); 40 | } 41 | } 42 | // We'll preserve newlines. 43 | n = min(n, text.find("\n")); 44 | 45 | result += text.substr(0, n); 46 | text.erase(0, n); 47 | boost::trim(text); 48 | 49 | if (!text.empty()) { 50 | result += "\n"; 51 | } 52 | } 53 | return result; 54 | } 55 | 56 | string fmt_pos_opt_short(std::string opt_name) 57 | { 58 | ostringstream s; 59 | s << "<" << opt_name << ">"; 60 | return s.str(); 61 | } 62 | 63 | string fmt_opt_short(boost::shared_ptr option) 64 | { 65 | ostringstream s; 66 | bool required = option->semantic()->is_required(); 67 | if (!required) { 68 | s << "["; 69 | } 70 | s << "--" << option->long_name(); 71 | if (option->semantic()->max_tokens() != 0) { 72 | s << " "; 73 | } 74 | if (!required) { 75 | s << "]"; 76 | } 77 | return s.str(); 78 | } 79 | 80 | void print_option_details( 81 | po::positional_options_description &positional_opts, 82 | po::options_description &named_opts, 83 | size_t& lcol_width, 84 | std::string optional_args_title = "Optional arguments") 85 | { 86 | map pos_opt_desc; 87 | 88 | // Right column margin. 89 | size_t option_indent = 2; 90 | lcol_width = max(lcol_width, Client::MIN_LCOLUMN_WIDTH); 91 | string last_pos_opt; 92 | for (unsigned int i = 0; i < positional_opts.max_total_count(); i++) 93 | { 94 | auto opt_name = positional_opts.name_for_position(i); 95 | if (opt_name == last_pos_opt) { 96 | break; 97 | } 98 | // Determine the margin and prepare a mapping with positional 99 | // option descriptions. The actual descriptions will be filled 100 | // when handling the named opts. 101 | pos_opt_desc.insert(pair(opt_name, "")); 102 | lcol_width = max(lcol_width, opt_name.length() + 3 + option_indent); 103 | } 104 | 105 | size_t optional_arg_count = 0; 106 | for (auto named_opt : named_opts.options()) 107 | { 108 | auto opt_name = named_opt->long_name(); 109 | auto it = pos_opt_desc.find(opt_name); 110 | if (it != pos_opt_desc.end()) { 111 | it->second = named_opt->description(); 112 | continue; 113 | } 114 | optional_arg_count += 1; 115 | lcol_width = max(lcol_width, 116 | fmt_opt_short(named_opt).length() + 1 + option_indent); 117 | } 118 | 119 | if (!pos_opt_desc.empty()) { 120 | cout << "Positional arguments:" << endl; 121 | for (auto pos_opt : pos_opt_desc) { 122 | string formatted_desc = fmt_text( 123 | lcol_width, 124 | max(Client::LINE_WIDTH - lcol_width, Client::LINE_WIDTH / 3), 125 | pos_opt.second); 126 | cout << left << string(option_indent, ' ') 127 | << setw(lcol_width - option_indent) 128 | << fmt_pos_opt_short(pos_opt.first) 129 | << formatted_desc << endl; 130 | } 131 | cout << endl; 132 | } 133 | 134 | if (optional_arg_count) { 135 | cout << optional_args_title << ":" << endl; 136 | for (auto named_opt : named_opts.options()) 137 | { 138 | auto opt_name = named_opt->long_name(); 139 | // Already printed. 140 | if (pos_opt_desc.find(opt_name) != pos_opt_desc.end()) { 141 | continue; 142 | } 143 | string formatted_desc = fmt_text( 144 | lcol_width, 145 | max(Client::LINE_WIDTH - lcol_width, Client::LINE_WIDTH / 3), 146 | named_opt->description()); 147 | cout << left 148 | << string(option_indent, ' ') 149 | << setw(lcol_width - option_indent) 150 | << fmt_opt_short(named_opt) 151 | << formatted_desc << endl; 152 | } 153 | cout << endl; 154 | } 155 | } 156 | 157 | void print_command_usage( 158 | string binary_name, 159 | string command_name, 160 | po::positional_options_description &positional_opts, 161 | po::options_description &named_opts) 162 | { 163 | string usage_str = "Usage: " + binary_name + " " + command_name; 164 | size_t margin = usage_str.length() + 1; 165 | 166 | set pos_opt_names; 167 | ostringstream pos_stream; 168 | 169 | string last_pos_opt; 170 | for (unsigned int i = 0; i < positional_opts.max_total_count(); i++) 171 | { 172 | auto opt_name = positional_opts.name_for_position(i); 173 | if (opt_name == last_pos_opt) { 174 | pos_stream << " [<" << opt_name << "> ...]"; 175 | break; 176 | } 177 | pos_stream << "<" << opt_name << "> "; 178 | pos_opt_names.insert(opt_name); 179 | } 180 | 181 | ostringstream ops_stream; 182 | for (auto named_opt : named_opts.options()) 183 | { 184 | auto opt_name = named_opt->long_name(); 185 | if (pos_opt_names.find(opt_name) != pos_opt_names.end()) { 186 | continue; 187 | } 188 | ops_stream << fmt_opt_short(named_opt) << " "; 189 | } 190 | 191 | string formatted_opts = fmt_text( 192 | margin, 193 | max(Client::LINE_WIDTH - margin - 1, Client::LINE_WIDTH / 3), 194 | boost::algorithm::join(vector({pos_stream.str(), ops_stream.str()}), "\n")); 195 | 196 | cout << usage_str << " " << formatted_opts << endl; 197 | } 198 | 199 | DWORD print_command_help(string command_name) 200 | { 201 | auto command = Client::get_command(command_name); 202 | if (!command) { 203 | cerr << "Unknown command: " << command_name << endl; 204 | return ERROR_INVALID_PARAMETER; 205 | } 206 | 207 | po::positional_options_description positional_opts; 208 | po::options_description named_opts; 209 | 210 | if (command->get_options) { 211 | command->get_options(positional_opts, named_opts); 212 | } 213 | 214 | print_command_usage("wnbd-client", command->name, 215 | positional_opts, named_opts); 216 | 217 | cout << endl << fmt_text(0, Client::LINE_WIDTH, command->description) 218 | << endl << endl; 219 | 220 | // Use a consistent indentation for option groups. 221 | size_t lcol_width = Client::MIN_LCOLUMN_WIDTH; 222 | print_option_details(positional_opts, named_opts, lcol_width); 223 | 224 | // There aren't common positional arguments. 225 | po::positional_options_description common_pos_opts; 226 | po::options_description common_opts; 227 | Client::get_common_options(common_opts); 228 | print_option_details(common_pos_opts, common_opts, lcol_width, 229 | "Common arguments"); 230 | 231 | return 0; 232 | } 233 | 234 | void print_commands() 235 | { 236 | cout << "wnbd-client commands: " << endl << endl; 237 | 238 | size_t name_col_width = 0; 239 | for (Client::Command *command : Client::commands) 240 | { 241 | size_t width = command->name.length(); 242 | if (!command->aliases.empty()) { 243 | for (auto alias: command->aliases) { 244 | width += alias.length(); 245 | } 246 | } 247 | width += (command->aliases.size() * 3); 248 | name_col_width = max(name_col_width, width); 249 | } 250 | for (Client::Command *command : Client::commands) 251 | { 252 | vector cmd_names; 253 | cmd_names.push_back(command->name); 254 | cmd_names.insert( 255 | cmd_names.end(), command->aliases.begin(), 256 | command->aliases.end()); 257 | string joined_cmd_names = boost::algorithm::join(cmd_names, " | "); 258 | string formatted_desc = fmt_text( 259 | name_col_width + 1, 260 | max(Client::LINE_WIDTH - name_col_width, Client::LINE_WIDTH / 3), 261 | command->description); 262 | cout << left << setw(name_col_width) << joined_cmd_names 263 | << " " << formatted_desc << endl; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /wnbd-client/usage.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * Licensed under LGPL-2.1 (see LICENSE) 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | DWORD print_command_help(std::string command_name); 12 | void print_commands(); 13 | -------------------------------------------------------------------------------- /wnbd-client/wnbd-client.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Analyze 6 | x64 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | x64 15 | 16 | 17 | 18 | 16.0 19 | {A9A19AD0-D8F6-4D41-9B82-375370C408D7} 20 | Win32Proj 21 | userspace 22 | 10.0 23 | 24 | 25 | 26 | Application 27 | true 28 | v142 29 | NotSet 30 | 31 | 32 | Application 33 | false 34 | v142 35 | true 36 | NotSet 37 | 38 | 39 | Application 40 | false 41 | v142 42 | true 43 | NotSet 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | false 62 | wnbd-client 63 | $(Platform)\$(Configuration)\obj\ 64 | 65 | 66 | false 67 | wnbd-client 68 | $(Platform)\$(Configuration)\obj\ 69 | true 70 | 71 | 72 | true 73 | wnbd-client 74 | $(Platform)\$(Configuration)\obj\ 75 | 76 | 77 | 78 | 79 | 80 | Level3 81 | true 82 | true 83 | true 84 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 85 | true 86 | ..\driver\;..\include;%(AdditionalIncludeDirectories) 87 | MultiThreaded 88 | stdcpp20 89 | 90 | 91 | Console 92 | true 93 | true 94 | true 95 | $(OutDir)\libwnbd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) 96 | $(OutDir)\pdb\$(ProjectName)\$(TargetName).pdb 97 | 98 | 99 | 100 | 101 | 102 | 103 | Level3 104 | true 105 | true 106 | true 107 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 108 | true 109 | ..\driver\;..\include;%(AdditionalIncludeDirectories) 110 | MultiThreaded 111 | stdcpp20 112 | 113 | 114 | Console 115 | true 116 | true 117 | true 118 | $(OutDir)\libwnbd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) 119 | $(OutDir)\pdb\$(ProjectName)\$(TargetName).pdb 120 | 121 | 122 | 123 | 124 | 125 | 126 | Level3 127 | true 128 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 129 | true 130 | ..\driver\;..\include;%(AdditionalIncludeDirectories) 131 | MultiThreadedDebug 132 | stdcpp20 133 | 134 | 135 | Console 136 | DebugFull 137 | $(OutDir)\libwnbd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) 138 | $(OutDir)\pdb\$(ProjectName)\$(TargetName).pdb 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | $(SolutionDir)\deps;$(CAExcludePath) 165 | 166 | --------------------------------------------------------------------------------