Our specialist who works on the trace rout department

September 17th, 2016

So from last night sometime, I couldn’t get at my email server. Nobody here could. From another net it worked fine. So I figure I need to talk to Bluehost.com, the hoster. I had a chat earlier today that I had to cut short due to other things. And due to me being close to completely losing it. So this is act II. The only thing that kept me sane during this hour-long confrontation with inanity was the thought of publishing it here. So here you are. Bluehost support in all its glory.

BTW, I had a hard time deciding if I should go with the current subject line or maybe “It will not be removed from blackhole”. Both are pretty good, I think. You’ll see why.

Oh, and the “3-point program” is repeated my Manish further down the dialog, so you’ll see it there.



Chat ID: 31908570

Initial Question: Hi, I’d like you to please unblacklist my network. The IP I need you to unlist is 109.228.nnn.nnn. I’ve gone through your 3-point program, nothing found. I do, however, suspect I know what’s happened. One of my machines had a corrupt email database and reloaded the whole thing, around 150,000 mails. It would have been very helpful if you could have confirmed that it was an IMAP overload, but I gather you can’t do that. Right? I’d also like to know how long it takes your blacklist to time out, i.e. how long it takes until a network is unlisted again without me having to ask you to. Martin

7:51:11 Manish Hello Martin, thank you for contacting support.My name is Manish. Sorry for the wait time.

7:51:21 Manish Are you looking to blacklist the IP?

7:52:30 Martin No, to remove it from the blacklist.

7:52:56 Manish Okay, let em check. Could I please get the primary domain name and the last 4 characters of your hosting / cPanel password for ownership verification purpose?

7:53:31 Martin ursecta.com … xxxx

7:53:51 Manish Thank you for Validating!

7:54:48 Manish Okay, I could see that the IP 109.228.nnn.nnn is not blacklisted.

7:56:03 Martin Earlier today someone else at your support said it was. And I still can’t connect from that IP. So what’s going on, then?

7:56:29 Manish You can check it in this link : http://mxtoolbox.com/SuperTool.aspx?action=blacklist%3a109.228.nnn.nnn&run=toolpage

7:57:49 Martin Ah, I see. You misunderstand me. My IP is blackholed at your server, so I can’t access ursecta.com through any protocols. I’m not talking about SMTP blacklisting.

7:58:33 Manish Okay, let me check this.

7:59:01 Manish Did you scan the local computer and also did you reset the Wifi password ?

7:59:42 Martin Yes, I did all those things. Got the list earlier today. Nothing there. As I said, the explanation is almost certainly the rebuild of my 150,000 emails yesterday.

8:00:13 Manish Can you please confirm the local IP using this link : http://www.bluehost.com/ip

8:01:10 Martin Yep,
8:01:18 Martin 109.228.nnn.nnn
8:01:40 Martin (Note, this is not the network I’m coming from now. I’ve got several here.)

8:09:57 Manish Sorry it is taking time.
8:10:16 Manish I need to contact to our specialist and I am waithing for the response from them.

8:10:29 Martin No problem. I’ll wait.

8:11:09 Manish Thanks!
8:16:39 Manish Still working on this.

8:16:47 Martin Ok

8:20:26 Manish Okay, To remove blackhole: Before we un-blackhole them, they need to get started on these things: 1. Scanning all computers/devices that use that router for malware. 2. Securing the wireless access point on the router (if it has one) or changing it’s password. 3. Asking the people that connect to that router if they’re running anything automated or manually doing things repeatedly that might have connected too many times and gotten them blocked. Please note that a lot of issues will come from ftp or email clients that have bad credentials for connecting to our server. They will continue to try and connect over and over resulting in us blocking the ip. They should also make sure that if they use ftp or emails clients that their settings are all correct. They might want to shut down those programs as well. Once they have taken those 3 steps, they should contact us back so we can unblock their IP address. They will also want to take note of any issues they corrected on their machines as if they don’t make any changes there is a good chance the problem is not fixed and they will get banned again.

8:21:07 Martin Yes, I’ve done all that. I already got that instruction a few hours back. We’re past that.

8:22:04 Manish One moment, our specialist have told me that all the process has not been completed.

8:22:26 Martin And what part would that be?

8:22:44 Manish I have asked them one moment
8:26:13 Manish okay as above we need the results. scan reset and as above provide the results of the scans from that IP

8:26:52 Martin Results? Of what?

8:28:39 Manish We will get one result when we trace out the IP. So to get that result you need to scan for the computer of the IP you have provided and then after scanning need to reset the wifi password. So once you scan please contact us after some time.

8:30:35 Martin That, if I may be so bold, doesn’t compute. It’s a router. The router has been checked, password changed. There are several computers behind that router. Most are OS X, or iOS. The OSX machines have Little Snitch, which is an outbound firewall and which has no abnormal traffic in its logs. The one Windows machine I have, I scanned with AVG, no problems.

8:31:07 Martin Even more, I already told you what caused the trip of the blackhole: the rebuild of 150,000 emails. Which is your point 3.

8:32:12 Manish Yes, But when we conduct the tracerout we got the error and it says that the local IP is not scanned properly. they need to scan it again and reset the wifi passwords.

8:33:22 Martin That, again, is simply impossible. You can’t get an error on a traceroute. In particular you can’t get trace route to tell you anything about what I’m scanning on my local network. That’s simply not how networking works. There is something deeply wrong with this explanation.

8:35:03 Manish Please make sure that you have scanned the systems. because our specialist who works on the trace rout department is not getting the accurate traceroute result.

8:37:11 Martin He’s not getting my router on traceroute (the last hop) because I don’t allow pings from the outside. Which is normal if you’re a little wee security concious. He should know that. And not getting a ping has nothing to do with scans for malware. He is, however, getting to bahnhof.se upstream router ( which is enough for this purpose. The simplest thing is to lift the block and I’ll let you know if that’s done. Takes me three seconds.

8:38:42 Manish OKay

8:39:04 Martin You know what… I just lowered my security for you. Now he should see pings and traceroutes.

8:39:55 Manish Let me check it out again. SO it will take some time for to connect them. Please be on hold.

8:47:25 Martin YES! Thank you. It’s pinging!

8:48:19 Manish Yes, it is done now.

8:48:20 Martin One question: after how long does the blackhole go away if you don’t intervene? Any idea?

8:48:24 Manish Is there anything else i can help you with?

8:48:42 Manish It will not be removed from blackhole.

8:48:57 Martin So it’s forever?

8:49:06 Manish Yes,

8:49:22 Martin That’s horrible. But thanks for fixing this.

8:49:43 Manish Welcome!


PS: I’m definitely looking for another host.

Autoresizing text view in table cells

August 11th, 2016

Continuing from the previous version, I upgraded to Swift 3 and Xcode 8 (beta 4). Most of the glitches are gone, but every now and then one shows up. So I’m not entirely sold on the whole thing yet. I’m still having doubts if it wouldn’t be simpler to just have the user enter text in a separate popup of some kind, and only display read-only views in the actual table. The problem with that is that it is a lot less intuitive and user-friendly. OTOH, it would work… I get a feeling Apple really doesn’t want us to do editing in text views in table cells. I don’t think any of Apple’s own apps do that.

One key thing is to have the table update (tableView.beginUpdates() – tableView.endUpdates()) cycle only be done from the controller, and not as a call from the table cell, else things go sideways quickly. Also, these updates shouldn’t happen on every cell update, else there’s a lot of twitching and jumping. What I’d really need to do is to set a flag in the controller, then call the table updates once the UI goes into idle. But I can’t find anything in UIKit that tells me the UI is in idle. Windows has that, but it doesn’t seem like iOS does, so I have to fake it using a timer. The table cells call “refresh()” when they change anything. The timer is recreated every time it gets called and only triggers a real update once it’s been left alone for a quarter of a second.

var runningTimer: Timer? = nil


func refresh() {

  if let timer = runningTimer {



  runningTimer = startTimer()



private func startTimer() -> Timer {

  returnTimer.scheduledTimer(timeInterval: 0.25, target: self,

      selector: #selector(MyTableViewController.DoTheRefresh),

      userInfo: nil, repeats: false)



private func stopTimer(_ timer: Timer) {

  if let timer = runningTimer {


    runningTimer = nil




func DoTheRefresh(_ timer: Timer) {





When typing in a field, you can have the cursor disappear behind the onscreen keyboard if you type long enough. I added a scrolling routine to take care of that:

// find out where the curson is and make sure it’s within

// the visible area of the tableView

// else typing a long text in a growing textView tends

// to let the cursor position slip behind the keyboard


private func makeCursorPositionVisible() {

  if let textRange = textView.selectedTextRange {

    let textPosition = textRange.end

    let caretRect = textView.caretRect(for: textPosition)

    if let caretRectInTableView = tableView?.convert(caretRect, from: textView) {

      let adjustedRect = CGRect(x: caretRectInTableView.minX,

                  y: caretRectInTableView.minY,

                  width: caretRectInTableView.width,

                  height: caretRectInTableView.height + tableView!.contentInset.top)

      // the scroll actually looks better to me without animation,

      // but YMMV

      tableView!.scrollRectToVisible(adjustedRect, animated: false)




The whole project is now on Github for your viewing pleasure.

Now with editing and rotation

August 1st, 2016

I went on and extended the autoresizing table cells to handle interface rotation and table editing as well. In both these cases, the width of the table cell content view changes, so the height of the text view needs to be recalculated. To achieve that, you need to wait to measure the new width of the text view until everything has settled down, but this turns out to be surprisingly difficult. The only functions I found that were called late enough in the view update cycle to give me the new definite text view width were didRotateFromInterfaceOrientation in the controller for rotations, and didTransitionToState in the table view cell for editing state changes.

In the controller:

override func didRotateFromInterfaceOrientation(

                fromInterfaceOrientation: UIInterfaceOrientation) {


  // after rotation, the cursor has a tendency to keep indicating editing

  // even though the current view doesn’t respond to typing

  // anymore. Ending editing cleans that up.



As you can see, I also had to add the endEditing() call to get rid of ambiguous editing state.

In the table view cell child class:

override func didTransitionToState(state: UITableViewCellStateMask) {




These two methods work fine, but there is one very irritating problem left, namely that the animations don’t coincide. When rotating or changing editing state, the built-in table animations happen first, and only then does the text view animate to its new size. I imagine you could wrap the animations in CATransition calls, but if you do that, you need to know beforehand how wide the text view will be, and to do that, you need to know the width of the table in both orientations, and the change in size of the content view of the table cell. I can find no properties in the Cocoa classes that can tell me any of this. As the project is now, I do find out after the fact what the new size is, but that’s too late. If anyone knows a better method, I’m all ears.

The entire project can be downloaded here. As before, it’s in Swift 2.x and Xcode 7.3.

Autoresizing table cells

July 25th, 2016

Extending the autoresizing of UITextView to table cells, this is how that looks.

First, let’s create a trivial model class and a storage class for that model:

class Item: NSObject {

  var text: String


  init(text: String) {

    self.text = text




class ItemStore {

  var allItems = [Item]()


  func createItem(text: String) -> Item {

    let newItem = Item(text: text)


    return newItem



  init() {

    for_in 0..<20 {

      createItem(“For ever and ever, this may be a great filler text. We will never know with absolute certainty, will we? Horray!”)




As you can see, I create 20 items all with the same text, just to be able to fill more than a screen in the table view. In the main storyboard, delete the default window and replace it all with a table view controller. Subclass the controller:

import UIKit


protocol RefreshableTableViewController {

  func refresh()



class MyTableViewController: UITableViewController, RefreshableTableViewController {

  var itemStore: ItemStore!


  override func viewDidLoad() {



    // move down the table view contents a bit


    let statusBarHeight = UIApplication.sharedApplication().statusBarFrame.height

    let insets = UIEdgeInsets(top: statusBarHeight, left: 0, bottom: 0, right: 0)

    tableView.contentInset = insets

    tableView.scrollIndicatorInsets = insets


    tableView.rowHeight = UITableViewAutomaticDimension

    tableView.estimatedRowHeight = 65



  override func tableView(tableView: UITableView,

               numberOfRowsInSection section: Int) -> Int {

    return itemStore.allItems.count



  override func tableView(tableView: UITableView,

                cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCellWithIdentifier(“ItemCell”,

                forIndexPath: indexPath) as! ItemCell


    // cell needs a delegate link back to the tableView controller, so it

    // can trigger refresh of row heights

    cell.tableController = self


    // hand the item to the cell. as it updates the item, the model store

    // is automatically kept up to date since it holds a reference to the item

    let item = itemStore.allItems[indexPath.row]



    return cell



  func refresh() {

    // after updating the size and constraints of the table view cell,

    // the table itself

    // must refresh to adjust the distance between the rows






  override func didRotateFromInterfaceOrientation(

                 fromInterfaceOrientation: UIInterfaceOrientation) {

    // updating all constraints and table rows after a rotation requires the heavy

    // hand of a full reload





From the application delegate, create the store, get the view controller and feed it the store:

  func application(application: UIApplication, didFinishLaunchingWithOptions

             launchOptions: [NSObject: AnyObject]?) -> Bool {

    // Override point for customization after application launch.


    let itemStore = ItemStore()


    let itemController = window!.rootViewController as! MyTableViewController

    itemController.itemStore = itemStore


    return true


Now, subclass UITableViewCell:

class ItemCell: UITableViewCell, UITextViewDelegate {


  @IBOutlet var textView: UITextView!

  @IBOutlet var heightConstraint: NSLayoutConstraint!


  var lastCalculatedTextHeight: CGFloat = 0.0

  var tableController: RefreshableTableViewController?

  var item: Item!     // must be class for reference semantics


  func useItem(item: Item) {

    self.item = item

    self.textView.text = item.text




  func updateMeasurements() {

    let newHeight = calculateHeightTextView(textView, forText: textView.text)

    heightConstraint.constant = newHeight



  func textViewDidChange(textView: UITextView) {

    // update the model with the current text

    item.text = textView.text



  func textView(textView: UITextView, shouldChangeTextInRange range: NSRange,

                    replacementText text: String) -> Bool {

    // it’s important that we measure the text and set the constraint *before*

    // the textview actually sets the new

    // text, else the scroll view will jitter and jump.

    // that’s why we catch “shouldChange” to get in ahead of that


    let newText = buildNewText(textView.text, range: range, replacementText: text)

    let newHeight = calculateHeightTextView(textView, forText: newText)


    return true



  func refreshIfNeeded(newHeight: CGFloat) {

    if newHeight != lastCalculatedTextHeight {

      lastCalculatedTextHeight = newHeight

      heightConstraint.constant = newHeight





  func buildNewText(oldText: String, range: NSRange,

                   replacementText: String) -> String {

    if let nsRange = oldText.rangeFromNSRange(range) {

      let newText = oldText.stringByReplacingCharactersInRange(nsRange,

                   withString: replacementText)

      return newText


    // if the range was invalid, just return the old text

    return oldText



  func calculateHeightTextView(textView: UITextView, forText: String) -> CGFloat {


    if let font = textView.font {

      let padding = textView.textContainer.lineFragmentPadding

      let width = textView.contentSize.width – 2 * padding


      let attributes = [NSFontAttributeName: font]

      let newRect = forText.boundingRectWithSize(

               CGSize(width: width, height: CGFloat.max),

               options: [.UsesLineFragmentOrigin, .UsesFontLeading],

               attributes: attributes, context: nil)

      let textInset = textView.textContainerInset

      let newHeight = newRect.height + textInset.bottom + textInset.top


      return newHeight



    // if we don’t know what else to say, just return the old height,

    // probably better than nothing

    NSLog(“failed to calculate height, no font?”)

    return textView.contentSize.height



This cell is designed in IB as the prototype cell. It needs to get constraints as follows:

The height constraint on the text view needs to get a priority less than 1000, e.g. 999, else it will give a conflict with a constraint that iOS will add on the fly when instantiating the cell. Interestingly, even though your own height constraint gets a lower priority than the system created constraint, still it will work fine.
You then need to bind the height constraint to the outlet in the ItemCell, and the text view to its outlet in the ItemCell, as well. The result looks like in this very brief movie I posted on Vimeo.
Interestingly, the size change happens with a smooth animation even though I didn’t animate the change in the size of the text view as I had to do in the previous project. The animation originates in the table view itself, so it comes for free.
Because there are a number of things you need to set correctly in Interface Builder, and some details I may have glossed over here, I’ve posted the entire project for your downloading pleasure. Again, as in the previous post, this is all Swift 2.x and XCode 7.3.

Autoresizing UITextView

July 24th, 2016

A problem I needed to solve was: while editing in a text view, I want the view to grow and shrink depending on the text entered. I also want this to work in conjunction with auto-layout.

Autosizing UITextView

This is something I’ve been fighting for some time, but now I sat down and spent enough time (and quite a bit of Stack Overflow reading) to get it to work. The first problem I kept having was finding all the parameters that determine the actual size of the text area so I could calculate it reliably. The other important problem was that most everything I tried made the text scroll up a little bit every now and then, making for an ugly jumping up and down of the whole text content. The solution I found by trial and error (mostly error) was to calculate and reset the height constraint before the text view actually set the new text, so it has to be done in shouldChangeTextInRange delegate method.

class ViewController: UIViewController, UITextViewDelegate {


  @IBOutlet weak var heightConstraint: NSLayoutConstraint!

  @IBOutlet weak var myTextView: UITextView!


  var lastCalculatedTextHeight: CGFloat = 0.0


  override func viewDidLoad() {





  func resizeTextView(textView: UITextView) {

    resizeTextView(textView, forText: textView.text)



  func resizeTextView(textView: UITextView, forText newText: String) {


    if let font = textView.font {


      let padding = textView.textContainer.lineFragmentPadding

      let width = textView.contentSize.width – 2 * padding


      let attributes = [NSFontAttributeName: font]


      let newRect = newText.boundingRectWithSize(

                     CGSize(width: width, height: CGFloat.max),

                     options: [.UsesLineFragmentOrigin, .UsesFontLeading],

                     attributes: attributes, context: nil)


      let textInset = textView.textContainerInset

      let newHeight = newRect.height + textInset.bottom + textInset.top


      if newHeight != lastCalculatedTextHeight {

        lastCalculatedTextHeight = newHeight

        UIView.animateWithDuration(0.5, animations: {

          self.heightConstraint.constant = newHeight







  // by doing this, by capturing the new size of the textView *before* the

  // text contents actually change, we can avoid having

  // the scroll view jump. We resize it before it sees it needs to grow.


  func textView(textView: UITextView, shouldChangeTextInRange range: NSRange,

                           replacementText text: String) -> Bool {

    let oldText = textView.text

    if let nsRange = oldText.rangeFromNSRange(range) {

      let newText = oldText.stringByReplacingCharactersInRange(nsRange,

                    withString: text)

      resizeTextView(textView, forText: newText)


    return true



You also need an extension on String for this:

import Foundation


extension String {

  func rangeFromNSRange(nsRange: NSRange) -> Range<String.Index>? {

    let from16 = utf16.startIndex.advancedBy(nsRange.location, limit: utf16.endIndex)

    let to16 = from16.advancedBy(nsRange.length, limit: utf16.endIndex)

    if let from = String.Index(from16, within: self),

             to = String.Index(to16, within: self) {

      return from ..< to


    return nil



  func NSRangeFromRange(range: Range<String.Index>) -> NSRange {

    let from = String.UTF16View.Index(range.startIndex, within: utf16)

    let to = String.UTF16View.Index(range.endIndex, within: utf16)

    return NSMakeRange(utf16.startIndex.distanceTo(from), from.distanceTo(to))




All of this is for Swift 2.x, by the way. Ah, another little neat thing: you put a height constraint on the text view through IB, then connect that constraint to an outlet. That’s the “heightConstraint” outlet at the top of the listing. (I never even thought of that before.)

You can find the whole project in a little zip-file.

Next little project is putting this to work in table cells, or maybe stack containers.

Medical IT crap, the why

July 11th, 2016

(Continuing from my previous post.)

I think the major problem is that buyers specify domain functionality, but not the huge list of “non-functional requirements”. So anyone fulfilling the functional requirements can sell their piece of crap as lowest bidder.

Looking at a modern application, non-functional requirements are stuff like resilience, redundancy, load management, the whole security thing, but also cut-and-paste in a myriad of formats, a number of import and export data formats, ability to quick switch between users, ability to save state and transfer user state from machine to machine, undo/redo, accessibility, error logging and fault management, adaptive user interface layouts, and on and on.

I’d estimate that all these non-functional requirements can easily be the largest part of the design and development of a modern application, but since medical apps are, apparantly, never specified with any of that, they’re artificially cheap, and, not to mince words, a huge pile of stinking crap.

It’s really easy to write an app that does one thing, but it’s much harder and more expensive to write an app that actually works in real environments and in conjunction with other applications. So, this is on the purchasers’ heads. Mainly.

A day in the life of “medical IT security”

July 9th, 2016

This article is an excellent description of some of the serious problems related to IT security in healthcare.

Even though medical staff actively circumvent “security” in a myriad inventive ways, it’s pretty clear that 99% of the blame lies with IT staff and vendors being completely out of touch with the actual institutional mission. To be able to create working and useable systems, you *must* understand and be part of the medical work. So far, I’ve met very few technologists even remotely interested in learning more about the profession they’re ostensibly meant to be serving. It boggles the mind, but not in a good way.

Some quotes:

“Unfortunately, all too often, with these tools, clinicians cannot do their job—and the medical mission trumps the security mission.”

“During a 14-hour day, the clinician estimated he spent almost 1.5 hours merely logging in.”

“…where clinicians view cyber security as an annoyance rather than as an essential part of patient safety and organizational mission.”

“A nurse reports that one hospital’s EMR prevented users from logging in if they were already logged in somewhere else, although it would not meaningfully identify where the offending session was.” 

This one, I’ve personally experienced when visiting another clinic. Time and time again. You then have to call back to the office and ask someone to reboot or even unplug the office computer, since it’s locked to my account and noone at the office is trusted with an admin password… Yes, I could have logged out before leaving, assuming I even knew I was going to be called elsewhere then. Yes, I could log out every time I left the office, but logging in took 5-10 minutes. So screen lock was the only viable solution.

“Many workarounds occur because the health IT itself can undermine the central mission of the clinician: serving patients.”

“As in other domains, clinicians would also create shadow systems operating in parallel to the health IT.”

Over here, patients are given full access to medical records over the ‘net, which leads physicians to write down less in the records. Think this through to its logical conclusion…

Server-side Swift

July 7th, 2016

This presentation from WWDC 2016 boggles the mind. It completely overturned all my concepts about server-side nodejs and javascript in general. If you’re into docker containers or anything of the kind, and you develop in Swift client-side, this must be seen.

Let’s hope the project doesn’t die. Let’s hope I didn’t overestimate this.

Somewhat dumb credit card region lock

June 16th, 2016

Visa has a neat feature where you can determine in which regions the card can be used. In my case, it’s “internet”, “Sweden”, “Nordic countries”, “Europe”, “North and central America”, “South America”, “Africa”, “Asia”, “Oceania”. You can set these through the credit card app (mine is from Volvo, of course).

So I disabled all regions except “Internet” and “Sweden”, planning on enabling other regions when I travel. 

Today I got a message from Netflix that they couldn’t charge my card. No explanation why. I called the card issuer and after some digging they explained to me that since I disabled “Europe”, Netflix got refused. Turns out that Netflix charges from region “Europe”, not “Internet”. More specifically from The Netherlands. Once I reenabled “Europe”, the charge went through.

Now, there are several problems with this. First of all, an internet based service like Netflix should be in the region “Internet”. Secondly, if it isn’t in “Internet”, they should at the very least tell us from which region they charge. I had no idea Netflix charges from The Netherlands. How could I? It’s not reasonable to expect us to check with the card issuer every time this happens, and have them go dig through logs (took them 10 minutes to find, so it wasn’t trivial).

Worst of all, this kind of thing implies that you’d better open up a lot of regions you’re not travelling to, since you don’t know from which regions different internet based companies do their charging.

Having the card processor issue meaningful error messages, not just “sorry we failed”, would definitely help a lot, too.

Now I hate Microsoft even more, part II

June 10th, 2016

Started my Win 10 instance under Parallels, and just one minute into working with it, I got this:

Parallels DesktopScreenSnapz095

That gave me 8 minutes to get my stuff in order. The time when this showed up was 21:18. So I clicked “Close” and just got the close box on my accounting program and the system rebooted. So much for the eight minutes, which turned into more like 10 seconds.

Note, BTW, no way to postpone this at all. Nothing.

The evil and arrogant fuckers.

Oh, lest you think Microsoft really let me save correctly, I got this after restart from my accounting program:

Parallels DesktopScreenSnapz096

It says, in Swedish: “The company wasn’t closed correctly. The company will now be optimized.” Meaning the index files will be rebuilt. 

Microsoft really doesn’t give a shit about our data. Fortunately, I hadn’t started entering anything so the rebuild worked out fine.