Thursday, 18 May 2017

Macro detection overload - Special Access Macros

This article talks about how one macro can call another macro if it exists, but if not, it can fallback to a default macro. This can be useful for behaviour vectoring or interception, debugging macros, etc.

Preferring Special Access Macros, but falling back to default macros

It would be nice if a C pre-processor macro can tell if another macro is defined.

It can... in a way. If you know in advance the name of the other macro you are looking it, you can do some comparison tricks as explained by Paul Fultz II in his C Preprocessor tricks, tips, and idioms.

But what if you don't know in advance what it will be called?

What if you are composing a combination of C macros and functions, where the developer can override a special case of the default behaviour for reasons of efficiency?

Maybe you have a cross-bar / plug-board, with data from a variety of sources, to undergo a variety of processing and be sent to a variety of destinations. Maybe you have a general route from a to b but sometimes want to drop in a specific optimisation.

Maybe you want to call a macro process(data, handler) where handler is the name of a macro or function used to emit the slowly generically processed data. Maybe process itself is actually an argument passed to you.

e.g.
#define work(process, data, handler) process(data, handler)
...
work(mangle, input, archive);
work(mangle, input, deliver);
A conceptual mangle might be defined (using GCC / clang statement expressions):
#define mangle(handler, data) ({
  for(int x, int i=0; ITERATATE_DATA(&x, i, data); i++) {
     handler(x);
  }
})
but perhaps it's not efficient for mangle to call archive for each processed input element, and we want to have a specialised function to mangle the input for archive instead.

After all there is a big difference between dest[index++]=c; and http_post_value(dest, c); when being called on every piece of data!

Of course the archive handler is simply a macro name and can't be passed as a parameter to a function (this isn't C++ templates you know...).

But that's OK, we write a specific function to mangle the input for the archive output, by calling the archive macro inside the function:
void mangle_archive(void* data) {
  void* buffer = bulk_mangle_to_buffer(data);
  archive(buffer);
  mangle_free(buffer);
}
That's nice.

But we'd like work(mangle, input, archive) to expand to call function mangle_archive(data) but we'd still like work(mangle, input, deliver) to expand to call the generic macro mangle(data, deliver) because it's not worth writing a more efficient handler to that. (But we might write one later, and if we did, we would want it to just work).

Can we do this? The answer is yes!

Whoa!

Now the difficulty is that (unlike in Paul's example) that we don't know in advance all the different handlers or processes that the user might have, so we can't go declaring in advance a load of macros like COMPARE_foo in advance. (Furthermore this is an ugly requirement to put on the author.)

However, in our case we can require that the special macro body (with which to override the default behaviour) should be entirely enclosed in parenthesis.

So while mangle_archive may well ultimately be a function, we will also define a macro wrapper thus:
#define mangle_archive(...) (mangle_archive(__VA_ARGS__))
Or something like that. As long as it is a macro whose body is in parenthesis and whose name is formed by joining mangle with archive. How does this help?

We have mangle and archive as input parameters, and if we can construct the name mangle_archive from mangle and archive, and then attempt to invoke it (as a macro, with the right number of parameters) it would result in (mangle_archive(...)) which (as the more enlightened will excitedly note) is an expression in parenthesis.

As anybody who has been reading Fultz2Heathcote and Gustedt will know, a well formed expression contained within parenthesis can be made to disappear entirely if prefixed by something such as the name of a macro which was defined thus:
#define VANISH(...)
e.g., with the right amount of coaxing:
VANISH (mangle_archive(__VA_ARGS__))
is quite easily persuaded to be nothing at all. And we can detect nothing at all. (See Jen's piece Detect empty macro arguments).

Clearly, if mangle_archive were not defined, we would have:
VANISH mangle_archive(__VA_ARGS__) which clearly won't be persuaded to vanish, and if mangle_archive were defined to something (not entirely) enclosed in parenthesis, we might have:
VANISH something (not entirely) enclosed in parenthesis
which clearly won't vanish either.

So to detect if the developer has defined a valid overriding macro, we simply attempt to invoke the macro with a VANISHing prefix and then use standard mechanisms already discovered to see if anything is left. (Using EVAL, naturally).

The actual trick is that rather than
#define VANISH(...)
we leave a comma (thanks Jens)
#define MAGIC_EAT_PARENTHESIS(...) ,
And put this at the start of an argument list to be followed by
MAGIC_MAKE_NAME(FUNC, IMPLEMENTATION),FUNC
We then grab the second argument with
#define MAGIC_DETECT_MACRO_(_, RESULT, ...) RESULT
and whether that is the concatenation of the process and handler, or just the process depends on whether or not an extra comma was inserted which would offset the first argument to become the second argument.

The resultant call to the overriding macro is called with the same arguments as would be supplied to the default macro, including the name of the handler. Of course, the handler is implicit now, being defined in the overriding function body, so the overriding macro can dispose of this (perhaps after a static assert) before calling the overriding function.
The simplified full solution is here.

MAGIC_DISPATCH takes at least two or three arguments, which are:

  1. default process name
  2. output handler, which when joined to the default process name becomes a potential overriding process name
  3. other arguments to pass to whichever of the processes is selected, should include at least the handler as the default macro will need to know where to send the data.
#define MAGIC_MAKE_NAME_(a, b) a##_##b
#define MAGIC_MAKE_NAME(a, b) MAGIC_MAKE_NAME_(a, b)
#define MAGIC_EMPTY()
#define MAGIC_DEFER(id) id MAGIC_EMPTY()
#define MAGIC_EVAL(...) __VA_ARGS__
#define MAGIC_EAT_PARENTHESIS(...) ,
#define MAGIC_DETECT_MACRO_(_, RESULT, ...) RESULT
#define MAGIC_DETECT_MACRO(...) MAGIC_DETECT_MACRO_(__VA_ARGS__)
#define MAGIC_DISPATCH_(FUNC, IMPLEMENTATION, ...) \
        MAGIC_DEFER(MAGIC_DETECT_MACRO) \
              (MAGIC_EAT_PARENTHESIS MAGIC_MAKE_NAME(FUNC, IMPLEMENTATION)(__VA_ARGS__) \
               MAGIC_MAKE_NAME(FUNC, IMPLEMENTATION),FUNC)\
        (__VA_ARGS__)

#define MAGIC_DISPATCH(...) MAGIC_EVAL(MAGIC_DISPATCH_(__VA_ARGS__))

#define work(process, data, handler) MAGIC_DISPATCH(process, handler, handler, data)
#define mangle_archive(handler, ...) (STATIC_ASSERT(#handler == "archive"), do_mangle_archive(__VA_ARGS__))

work(mangle, data, archive); // This will be overridden with mangle_archive
work(mangle, data, deliver); // This will call the default mangle
giving the output:
(STATIC_ASSERT("archive" == "archive"), do_mangle_archive(data));
mangle(deliver, data);
A last minute entry:
#define mangle_deliver(handler, data) (crunch_data(data))
work(mangle, data, deliver); // this will be overridden with mangle_deliver
gives:
(crunch_data(data));
If this looks too theoretical, consider that handler could be a list whose first member is the macro to call and whose other members could be trailing (or leading) arguments that are inserted into the process invocation. Such a handler would be decomposed by the invoker work to form a correct argument set for MAGIC_DISPATCH.

In such decomposition, these macros may prove useful.
#define MAGIC_LIST(...) (__VA_ARGS__)
#define MAGIC_UNLIST(...) __VA_ARGS__ 
#define MAGIC_UNLIST_FIRST(FIRST, ...) FIRST
though a more powerful level of EVAL may be required.

Much thanks to Paul Fultz II (C Preprocessor tricks, tips, and idioms), Jonathan Heathcote ( C Pre-Processor Magic), and particularly Jens Gustedt (P99 Macros for Emulation of C11), for documenting their work, which I have read, confused over, and slept on for many a night.

Tuesday, 25 April 2017

Too many arguments; or: keyword arguments in C

Sometimes you extend a generic helper function and it starts to take too many optional arguments, which can be left as NULL if they don't matter.

Maybe your function formats and signs a message that could have quite a variety of fields.

int sign(struct key *key, const char *this, const char *that)
{
    x_sign(key, this);
    ...
}
...

int result = sign(key, "This", "That");

and then you find yourself wanting to add const char* other.

Yeah... and there will be a lot of caller code to fixup (unless you want to use the other macro trick for default arguments, which I'll write up later). 

Or will there?

struct sign_args {
    const char* this;
    const char* that;
    const char* other;
};

int sign(struct key *key, struct sign_args *args)
#define sign(key, ...) sign(key, &(struct sign_args){ __VA_ARGS__ })
{
    x_sign(key,args->this);
    ...
}
and it is called the same way:

sign(key, "This", "That", "Other");

but you also get keyword arguments for free!

sign(key, .other = "Other"); // leaves this & that NULL

And this expands out to a valid compound literal expression. The temporary struct (an lvalue) is created (probably on the stack) and disposed of automatically at the end of the expression.

sign(key, &(struct sign_args){ .other = "Other" });

Another advantage is that now as your arguments are passed as a pointer, it is very easy to tunnel them all through the usual void* callback systems. In the case below, it briefly obtains an interactively unlocked key to sign with:

int batch_sign(struct key *key, struct sign_args *args);
int sign(struct key *key, struct sign_args *args) 
#define sign(key, ...) sign(key, &(struct sign_args){ __VA_ARGS__})
{
  x_with_unlocked_key(key, batch_sign, args);
  ...
}

Is it efficient? Every time the args are passed, only a pointer is passed, so that can be efficient; but the struct does have to be created once and may be a little more work than passing a struct as an argument or passing the arguments.

The main points to note are that

  • the original arguments should keep their same order at the start of the args struct
  • as the macro has the same name as your function, be sure to define it AFTER your function
  • the arguments are passed by pointer and so shared as they are tunnelled.
    This can make it easy to pass back a temporary value from an auto-variable. 
Of course you don't have to pass a pointer to the struct, you could pass the struct, by removing the & from the macro:

int sign(struct key *key, struct sign_args *args)
#define sign(key, ...) sign(key, (struct sign_args){ __VA_ARGS__ })
{
    x_sign(key,args.this);
    ...
}
it is called the same way:

sign(key, "This", "That", "Other");

but expands out slightly differently, passing the struct instead of a pointer to it, e.g.:

sign(key, .other = "Other"); // leaves this & that NULL

which this expands out to:

sign(key, (struct sign_args){ .other = "Other" });

Of course there is still nothing to prevent you from passing &args via a callback, or making and passing a copy.

Thursday, 26 January 2017

Trampolines on MIPS for GCC nested functions under VxWorks

Taking the address of a GCC nested function generates a trampoline  on most platforms so that the stack frame address can be recovered during the callback.

Nested functions are a GCC extension to C (available for Pascal), with an alternative of code blocks under clang.

Trampolines are described in Lexical Closures for C++ (Thomas M. Breuel, USENIX C++ Conference Proceedings, October 17-21, 1988), and this trampoline is a piece of generated code that can recover a stack frame pointer or any other useful value by loading a literal before making a jump to the callback address, so that auto variables of the containing function can be accessed in their stack frame.

It means that your callback can recover any number of auto-variables without the need for a cookie pointer or an associated struct.

The MIPS trampoline structure is explained here by Ian Lance Taylor in 2006.

On Systems with a unified cache, data-writes to generate the code automatically become visible to the instruction cache, but on other systems (MIPS, ARM) the data cache and instruction cache may be independent, so after generating the trampoline, the data cache must be flushed and the instruction cache invalidated for that region, to be sure that the CPU will read the newly generated instructions.

This Arm Community post explains the difficulty quite well, although do not expect the specification for the __clear_cache builtin function to have the same calling signature as the _flush_cache function whose calling is generated by a set of definable macros.

The _flush_cache function is system specific, you need whatever flushes the data cache and invalidates the instruction cache properly across all CPU's for your target system.

If it were easy, the compiler would have done it; but it doesn't know how to clear the caches for whatever system you might be targeting.

The proper way to do this on a MIPS system is not clear. There may be privileged instructions required, maybe user instructions such as SYNCI will work.

Since GCC 3.1 GCC has support for the -msynci option
GCC now supports an -msynci option, which specifies that synci is enough to flush the instruction cache, without help from the operating system. GCC uses this information to optimize automatically-generated cache flush operations, such as those used for nested functions in C. There is also a --with-synci configure-time option, which makes -msynci the default.
Maybe the OS kernel exports a system call or function to deal with this, so unless this is specified, the GCC nested function trampolines on MIPS under VxWorks tend to fail at link time, like this:

(.text+0x146370): undefined reference to `_flush_cache'
vxWorks.bin: In function `zig':
(.text+0x146370): relocation truncated to fit: R_MIPS_26 against `_flush_cache'

because the compiler has no real idea how to flush the caches on your MIPS system, and this function is not provided.

I can redefine the name of the missing function, so it calls the cache flush function on my system with this compiler option:
-mflush-func=really_flush_cache

(.text+0x146370): undefined reference to `really_flush_cache'
vxWorks.bin: In function `zig':
(.text+0x146370): relocation truncated to fit: R_MIPS_26 against `really_flush_cache'

If only I knew what the name of the cache flush function was, and that it had a matching calling signature.

Flush function signature

The documentation is very poor on the arguments to be provided to the flush cache function, and this seems to vary from platform to platform.

In the documentation, of special relevance are CLEAR_INSN_CACHE and TARGET_TRAMPOLINE_INIT which in one case is given this definition:

#define CLEAR_INSN_CACHE(beg, end) mips_sync_icache (beg, end - beg)

By adding debugging to inspect the arguments of a flush-cache function wrapper, I see that this matches the arguments passed when compiling under my VxWorks tool chain; but given that those macros can be re-defined it may not match GCC building for a different system so I must acknowledge that my flush function is not portable.

Possibly a portable implementation is given here, and I note that other JIT systems (e.g. SLJT used by PCRE-SLJT) and code generators (libffcall) attempt to provide their own portable implementations but which may not work on some multi-core systems.

Cache Operations

Linux

#include <asm/cachectl.h>
int cacheflush(char *addr, int nbytes, int cache);

VxWorks

VxWorks provides cacheInvalidate, cacheFlush and cacheClear (invalidate and flush); and particularly cacheTextUpdate which seems just what we want:

If cacheTextUpdate works, we could just have the compiler option:

-mflush-func=cacheTextUpdate

Or we might prefer a wrapper that allows us to vary it, or add debugging statements:

#include <cacheLib.h>

void _flush_cache(char* beg, size_t bytes) {
    cacheFlush(DATA_CACHE, beg, bytes);
    cacheInvalidate(INSTRUCTION_CACHE, beg, bytes);
}

or

#include <cacheLib.h>
void _flush_cache(char* beg, size_t bytes) {
    cacheTextUpdate(beg, bytes);
}

A clue, search for cacheTextUpdate in this initial attempt to add MIPS vxWorks support: 

It Lives!

Does it work? Well, the trampoline works; but is it treating the cache properly or will it randomly fail? I don't know.

#include <cacheLib.h>

void _flush_cache(char* beg, size_t bytes) {
    cacheTextUpdate(beg, bytes);
}

void zag(void(*zog)(void)) {
    printf("ZAG\n");
    zog();
}

void zig() {
  const char* where = NULL;

  void zog() {
    printf("ZOG from %s\n", where);
  }

  where = __FUNCTION__;

  printf("ZIG\n");
  zag(zog);
}

Call function zag( ) to see

ZIG
ZAG
ZOG from zig

If you want to see the generated trampoline code, use GCC flags -save-temps -fverbose-asm and then look at the .s file.

An end note for smug x86 users

From: http://lkml.iu.edu/hypermail/linux/kernel/0806.0/1702.html
If you do the sub-word write using a regular store, you are now invoking the _one_ non-coherent part of the x86 memory pipeline: the store buffer. Normal stores can (and will) be forwarded to subsequent loads from the store buffer, and they are not strongly ordered wrt cache coherency while they are buffered.
...
...if you do a regular store to a partial word, with no serializing instructions between that and a subsequent load of the whole word, the value of the store can be bypassed from the store buffer, and the load from the other part of the word can be carried out _before_ the store has actually gotten that cacheline exclusively!
So when you do
  movb reg,(byteptr)
  movl (byteptr),reg
you may actually get old data in the upper 24 bits, along with new data in the lower 8.
I think.
Anyway, be careful. The cacheline itself will always be coherent, but the store buffer is not going to be part of the coherency rules, and without serialization (or locked ops), you _are_ going to invoke the store buffer!

Wednesday, 28 December 2016

Augmenting Apple iCloud PhotoStream

As discussed previously, if you want to share in the photos of an Apple user, you have to do it the Apple way.

But once my windows PC has an incrementally downloading folder of photos, I want to upload them to Google Photos where everyone can view them.

I can upload the photos, but Google Photo's uses the file system dates when ordering the photos and ignores any timestamps in the exif tags; so somthing needs coding to extract that information from the tags to touch the files.

Furthermore, photos (and more likely, movies) that aren't tagged are totally without timestamp information.

And yet... iCloud photos viewer on windows has this information, along with comments -- none of which is stored in the images or movies.

The task

My self-appointed task is to find the source of this information for iCloud Photostream, embed it into the images and movies, and update file timestamps with it prior to upload to Google Photos.

Clues

There are some clues here: https://www.braxtonehle.com/posts/replicating-shared-photostream-to-dropbox/ but I'd already got that far by using Process Explorer to examine what files iCloud Photo was accessing.

The App

My original intention was to write a window app to upload to the Windows App Store, as the only one there to help with this seems highly priced at 

But having examined the database schema I see that this could be about 10 lines of bash scripting that works for just me; is it worth the extra work to produce a windows app?

No, but I'll do both anyway; downloading Visual Studio Express Community Edition

The bash script

The bash script is plain sqlite3 query, with the results piping into a loop at updates each file; with the usual bash presumptions that filenames won't contain a newline character, etc.

#! /bin/bash

USERDIR=/mnt/c/Users
WINUSER=flipflap
ICLOUD=AppData/Roaming/Apple\ Computer/MediaStream
DBNAME=local.db

DB="$USERDIR/$WINUSER/$ICLOUD/$DBNAME"

sq() {
  sqlite3 -line "$DB" "$@"
}

albums() {
  sq "select albumName from MSASAlbums"
}

photos() {
  sq "select assetfilepath, caption, downloaded, deleted, datetime(dateCreated + 978307200, 'unixepoch') || 'Z' as datetime, createdbyme from MSASAlbumAssets left join MSASAlbums on MSASAlbumAssets.albumGuid = MSASAlbums.albumGuid where albumName = '$1' order by dateCreated"
}

readRecord() {
  local field value count
  while read -r field _ value && test -n "$field"
  do printf -v "$field" "%s" "$value"
     count=$(( count +1 ))
  done
  test -n "$count"
}

updatePhotos() {
  while readRecord
  do file="${assetfilepath//C://mnt/c}"
     file="${file//\\//}"
     echo update "$datetime $file"
     touch -c -d "$datetime" "$file"
  done
}


if test -z "$1"
then albums
     exit
fi

photos "$@" | updatePhotos

And that works well enough.

Wednesday, 14 December 2016

Two touchpads for one

http://askubuntu.com/questions/783838/disable-touchpad-while-typing-do-not-work

Solution was to add in /etc/modprobe.d/blacklist.conf
blacklist i2c_designware-platform
And reboot the system. After that syndaemon works fine.
or

https://ubuntuforums.org/showthread.php?t=2316240

/usr/share/X11/xorg.conf.d/51-synaptics-quirks.conf
and added this entry:

Code:
# Disable generic Synaptics device, as we're using
# "DLL0704:01 06CB:76AE Touchpad"
# Having multiple touchpad devices running confuses syndaemon
Section "InputClass"
        Identifier "SynPS/2 Synaptics TouchPad"
        MatchProduct "SynPS/2 Synaptics TouchPad"
        MatchIsTouchpad "on"
        MatchOS "Linux"
        MatchDevicePath "/dev/input/event*"
        Option "Ignore" "on"
EndSection



Wednesday, 7 December 2016

What is wrong with Apple?

Apple make easy things very very hard

Sharing photos is easy with google


If I want to share my photos with someone who has a google account, I just name them in the share dialog, and then they can see my photos, via their own google account from any web browser.

Or if they don't have a google account, I can share the "link" to the album so that any one who has the hard-to-guess link can see my photos.

Anyone with a vaguely modern web browser.

That's all that's needed for 21st century communication.

But not with Apple


A family member uses an ipad and wants to share photos with me from her "photo stream".

So they send me an invitation to subscribe to their  photostream.

Luckily I have an icloud account from when I tried to use itunes to buy music (that's another story).

And I click on the link, to be greeted with:
To subscribe to ... photo stream on your iPhone, iPad, iPod touch or Mac, open your invitation in the Mail app and click the Subscribe button in the message.
To subscribe you need to be signed in to iCloud on:
  • an iPhone, iPad, or iPod touch with iOS 6 or later or
  • a Mac with macOS 10.8.2 or later and iPhoto 9.4 or Aperture 3.4 or later
I actually need to possess an apple device to view some photographs that someone has taken using their apple device!

I get my paws on an apple device

I manage to lay my hands on a MacBook Air. I give it all the updates. It's running Lion, the latest release of MacOS that it can run.

I install iCloud.

Do you want to guess if I can subscribe to the photostream?

I can't -- I need iPhoto 9. Free upgrades to iPhoto 9.4 are available if I have iPhoto 9, but I don't. It has iPhoto 8.

I can't buy iPhoto 9 from the Apple app store because it has been discontinued.

Apple won't let me pay them the money that they are extorting from me because... it isn't enough.

I track down iPhoto 9


I find that I can get iPhoto 9 if I buy a CDROM containing iLife 11. (iLife 11 has also been discontinued from the app store).

iLife 11 typically sells for $50 or £50 on Amazon, but I track down a copy for around £20 on ebay.

I'm not confident, and neither is my wife.

And then I notice, iPhoto 9 could possibly not be enough.

The small print in the email says: with macOS 10.8.2 or later
and I only have macOS 10.6.6 (Lion) and this MacBook Air  won't take a newer version.

So now I'm an apple user (with this MacBook Air I got hold of) and I still can't view the photostream. I need to shell out a few hundred pounds to but a newer macbook or ipad.

But iCloud is available for Windows.

iCloud for Windows

I install iCloud for windows.

It doesn't work.

After logging in (authenticating me successfully against the apple authentication servers) it then declares that I am not connected to the internet. c_a_murphy4 comments:
I hope you receive more professional support than I did. Their senior team took more than a week with this issue and then blamed Microsoft (in a rather backhand way by saying there was nothing more they could do from an Apple standpoint). I called up Microsoft, and they in turn blamed Apple. If you ever do find a solution to this problem, please let me know. Apple and Microsoft just aren't coming through with the goods - at least not for me.
Some people seem to suggest that it is only the windows 10 anniversary edition that has this problem. Well I'm not downgrading.

Windows 7

So I try again on an old windows 7 laptop and get exactly the same problem.

I suppose I can blame Apple that now it's not just windows 10 but apparently no version of windows can run the latest version of their software, and it's not there fault.

Taking advice, I downgrade to the previous version of iCloud for windows.

This time  get the Error occurred during authentication error.

So I try the previous version of iCloud for windows.

Surely one point Apple have managed to produce working software for windows? And it works.

But no photostream

Now iCloud works but isn't any use. The photostream subscription has to be accepted as describe in the sucks-be-to-you message I had when I originally tried to subscribe from an unclean non-apple system:
a Mac with macOS 10.8.2 or later and iPhoto 9.4 or Aperture 3.4 or later
So I lookup virtual mac machines in the cloud. I could pay a few dollars an hour and perhaps get it working. Or pay $20 a month with the first day free and then cancel right away.

Via a friend, I get long distance access to a mac, with all the right software, and the photostream subscription is accepted.

Back to windows

Now the photostream subscription is accepted I can begin to download it on the windows 7 PC and then use google photo uploader to sync that folder to a photo album.

Back to google

And so now I can share the google photo album with other non-apple family members.

But this is normal for Apple

They break your stuff on purpose

If a you sacrifice at the wrong altar and have a non-Apple vendor repair your phone, Apple prevent your phone from working.

If you jailbreak your phone so that you can install software that you didn't pay Apple for, they will brick your phone on the next firmware update.

I showed here how an Apple user with a MacBook Air is prevented from subscribing to the photostream of another Apple user, because they didn't sacrifice recently enough at the Apple altar.

But Apple made a mistake 

Their new much-cursed top-of-the-range laptop that is missing a load of ports -- no HDMI, no USB2, no sdcard, etc

Now some independent developers funded by kickstarter have put together The HyperDrive.
It’s a $100 dongle that slips neatly into both USB Type-C slots to give you a whole lot more connectivity. Not only do you get your two USB Type-C ports back, but you gain a couple of USB 3.1 ports; a microSD and SD slot; and an HDMI video port.
Did you notice? "slips neatly into both USB Type-C slots"

Because of a stupid Apple oversight in providing standard USB-C ports users have overcome Apple! How are they going to stop that?

Of course the most annoying thing is that there will have been room inside the case to fit this dongle.

Afraid of perfection

Did you ever know a company so afraid of perfection that they spent so much time and effort deliberately lousing things up for their customers?

Or a bunch of captive customers so suffering from Stockholm Syndrome that they keep paying over much money for such rubbish?

Stockholm syndrome, or capture-bonding, is a psychological phenomenon first described in 1973 in which hostages express empathy and sympathy and have positive feelings toward their captors, sometimes to the point of defending and identifying with the captors. These feelings are generally considered irrational in light of the danger or risk endured by the victims, who essentially mistake a lack of abuse from their captors for an act of kindness.
And we are not seeing that much lack of abuse.
The FBI's Hostage Barricade Database System shows that roughly eight percent of victims show evidence of Stockholm syndrome.
And yet Apple market share is quite a bit more than 8%, around 12%.

I wonder, what is the excuse of the other 4%?