Saturday, February 25, 2017

Salvaging a Lost Soul: Recovering a Mlais MX28 after Factory Reset

As my Mlais MX28 has been serving me faithfully for nearly 3 years (with no cleanup managers, factory resets or anything of the sort), I thought it's time to give it a bit of cleansing (a factory reset). Turns out I should have been in too much of a haste (or call it overconfidence, of you prefer), and I didn't even Google the possible risks of a factory reset before hitting the Erase Phone button.

All went well, except that the phone refused to boot beyond the Mlais logo after restart. After about 10 minutes of waiting I became nervous enough to fire up an adb logcat to see what was going on. Things had become quite "loopy", with the same set of (mostly error) logs repeating over and over again.

Looking at an excerpt of the logs, it became clear that all the errors were based on a missing /data directory: a missing /data/nvram here, a missing /data/data there, and so on. After a bit of log analysis I came up with the following script for creating a minimal directory hierarchy for getting things past the bootloop:

adb wait-for-device && adb shell "mount -o rw,remount /; mkdir -p /data/misc/keystore; mkdir -p /data/misc/mblog; mkdir -p /data/anr; mkdir -p /data/aee_exp; mkdir -p /data/agps_supl; mkdir /data/app-lib; mkdir /data/dalvik-cache; mkdir /data/mdl; mkdir -p /data/media/obb; mkdir /data/misc/bluetooth; mkdir /data/misc/wifi; mkdir /data/data"

After the phone booted, I was greeted with a fully Chinese home screen, and the "factory defaults" for a Chinese phone if there was any: everything in Chinese, and nothing working quite the way it should:

  • Both SIMs were displaying "invalid IMEI" errors, and only SIM1 was actively in service.
  • SMSs were being received but getting rejected with an "insufficient memory" error.
  • No apps could be installed, due to the same "insufficient memory" error.
  • Bluetooth, WiFi, GPS, USB tethering all were busted.
  • Though you could change system settings, everything would get reset to the miserable "factory default" state after a restart.

After a while I managed to obtain internet connectivity by manually creating an APN profile for the active SIM. But there was no way to share the connection with my PC (as neither hotspots nor tethering were working). As I did not have any other source of internet access, I had to resort to the painful procedure of searching everything on my phone and trying them out on itself, via USB over an ADB session from my PC.

Unfortunately I happened to start my inquiry from the wrong end: invalid IMEI. The articles I came across put the blame on a corrupted NVRAM; some even claimed that there was no other solution than flashing a new NVRAM file, if your phone is MTK-based (which happened to be, in my case). As I didn't have access to a native Windows system, and almost all flashing tools are made for Windows, I thought I was out of options.

Luckily I came across this post that explains how to modify iptables rules for sharing internet connectivity (though not for Android). After a bit of customization I came up with the following script which, when run after enabling USB tethering on the phone, gave my PC internet access:

# I gave my laptop the IP 192.168.42.150 on the tether network
sudo ifconfig enp0s20f0u4 192.168.42.150
# my phone got the IP 192.168.42.129 when tethered
sudo route add default gw 192.168.42.129
adb shell iptables -F
adb shell iptables -t nat -F
adb shell "echo 1 > /proc/sys/net/ipv4/ip_forward"
adb shell iptables -t nat -A POSTROUTING -o ccmni0 -j MASQUERADE
adb shell iptables -A FORWARD -i ccmni0 -o rndis0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
adb shell iptables -A FORWARD -i rndis0 -o ccmni0 -j ACCEPT
echo "nameserver 127.0.1.1" | sudo tee /etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf

After this lifesaving discovery I started searching more openly, and went through several articles on the Android startup process to find any clue of the NVRAM (and general Android) initialization process. Though instructive, none of them could help me. Luckily, the "insufficient memory" errors and the initial missing-path errors involving /data led me to check why the partition was not getting mounted.

With help from this post I learned that my /data partition device was /dev/block/mmcblk0p7:

root@Mlais:/ # ls -l
...
lrwxrwxrwx root     root              1970-01-01 05:30 emmc@usrdata -> /dev/block/mmcblk0p7
...

And when I tried to scan and repair the partition, I got the following error, even with the -b option:

The superblock could not be read or does not describe a correct ext2
filesystem.  If the device is valid and it really contains an ext2
filesystem (and not swap or ufs or something else), then the superblock
is corrupt, and you might try running e2fsck with an alternate superblock:
    e2fsck -b 8193 

As suggested by a quick search, I then tried a mke2fs /dev/block/mmcblk0p7 to recreate the partition, and that finally did it!

Now my dear old MX28 is back in pristine condition, thanks to the robustness of the Linux core that powers it all along!

Tuesday, February 21, 2017

Giving Life to AsciiDoc Images: Highlight Image Areas on List Hover

Though AsciiDoc (AD) has become a popular documentation medium these days, most AD-based websites still remain traditional HTML (except for, perhaps, some Bootstrap/jQuery styling). While this may be due to the need to retain the build-agnostic structure of AD sources (e.g. the same AD source can generate a fairly well-formatted website as well as a well-organized PDF), sometimes it does not hurt to utilize some low-impact tricks to produce some icing, say, for web-hostable HTML output.

For example, the following trick can be used to generate HTML output with 'highlightable' images where specific areas of images get highlighted (with a coloured frame overlay) when hovering over some corresponding text elements (e.g. items in a list of instructions/descriptions associated with the image).

  1. Designate each image:: element with an ID having a known prefix (e.g. hover-).
  2. [hover-id]
    image::sample-image.png
    
  3. For the list of elements associated with each corresponding image, add an ID attribute before the list (as the list's ID), containing the coordinates to be highlighted when hovering over each item, in the following format:
  4. [hover-id;5,5,20,20;40,40,20,20;0,90,100,10]
    * top-left corner element
    * mid element
    * bottom bar element
    

    where hover-id is the ID of the image which should be highlighted on hover, and the next three semicolon-delimited fragments denote the highlight region in percentages of width and height, for each of the three list elements, in the format top,left,width,height; e.g. if sample-image.png is 200px by 100px, 5,5,20,20 indicates a highlight region starting at (5%, 5%) or (10px, 5px) on the image area, 20%×20% (20px×10px) in size.

  5. In order to inject the necessary highlight logic into the generated HTML, include the following JS snippet in the HTML source:
  6. var links = document.querySelectorAll("*[id^=hover]");	// the ID prefix you used earlier
    foreach(links, function(link) {
    	// create svg overlay for target image
    	var parts = link.id.split(/;\s+/);
    	var imgWrapper = document.getElementById(parts[1]).children[0];
    	var img = imgWrapper.children[0];
    	var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    	svg.setAttribute("id", parts[1] + "-overlay");
    	svg.setAttribute("style", "position: absolute");
    	imgWrapper.insertBefore(svg, img);
    
    	// resize svg to match img
    	setSize(svg, img);
    	window.addEventListener("resize", function() {
    		setSize(svg, img);
    	});
    
    	// register highlighting events for each entry
    	foreach(link.children[0].children, function(item, i) {
    		item.onmouseenter = function () {
    			drawRect(svg, parts[i + 2].split(","));
    		}
    	});
    });
    
    function drawRect(svg, dim) {
    	cleanup(svg);
    	var pWidth = svg.getAttribute("width") / 100;
    	var pHeight = svg.getAttribute("height") / 100
    	var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
    	rect.setAttribute("x", dim[0] * pWidth);
    	rect.setAttribute("y", dim[1] * pHeight);
    	rect.setAttribute("width", dim[2] * pWidth);
    	rect.setAttribute("height", dim[3] * pHeight);
    	rect.setAttribute("stroke", "red");
    	rect.setAttribute("fill-opacity", "0");	// transparent
    	svg.appendChild(rect);
    }
    
    function setSize(svg, img) {
    	cleanup(svg);
    	svg.setAttribute("width", img.width);
    	svg.setAttribute("height", img.height);
    }
    
    function cleanup(svg) {
    	while (svg.firstChild) {
    		svg.removeChild(svg.firstChild);
    	}
    }
    
    function foreach(list, fn) {
    	for (var i = 0; i < list.length; i++) {
    		fn(list[i], i);
    	}
    }
    

    For example, if you use AsciiDoctor to generate HTML, this can be done by saving the JS snippet into a file overlay.js and including the tag

    <script src="overlay.js" type="text/javascript"/>

    in a docinfo-footer.html which would be automatically added to the AsciiDoctor build output. Since the HTML would not be utilized in, say, a PDF build, the additional stuff won't cause any major problems.

Disclaimer: This is a very crude solution that has been tried out and tested in only a handful of pages on Firefox and Chrome; some major improvements and fixes may still be on the way!