21
\$\begingroup\$

XKCD: Sandwich

Challenge: Write a makefile, which would lead to this behavior:

% make me a sandwich
What? Make it yourself
% sudo make me a sandwich
Okay

Rules:

  1. Should work on a Linux machine, where the user isn't root but allowed to use sudo.
  2. Must not say Okay to any shorter make command (e.g. sudo make sandwich).
  3. The second command should work because of sudo, not because it's the second invocation.

Guidelines:

  1. Keep it short (after all, it's a Code Golf site).
  2. It would be nice if it would actually make a sandwich.
  3. Try to cleverly hide why sudo makes a difference.

The winner will be decided by votes.

\$\endgroup\$
  • 2
    \$\begingroup\$ savannah.gnu.org/bugs/?23273 \$\endgroup\$ – ardnew Jul 30 '13 at 23:26
  • \$\begingroup\$ @ardnew, unfortunately, I specifically requested to write a makefile, which, I think, invalidates solutions based on patching make. \$\endgroup\$ – ugoren Jul 31 '13 at 5:53
  • \$\begingroup\$ totally agree, @ugoren. didnt mean for that comment to dismiss the challenge or suggest a solution \$\endgroup\$ – ardnew Jul 31 '13 at 13:48
12
\$\begingroup\$

Okay, I'll bite. This doesn't necessarily adhere to guideline #3, but it does a fair job on the other two guidelines. It also cleans up after itself, as any good sandwich-maker should.

.SILENT:

%:%.c
        $(CC) -o $@ $^ && rm -f $^

default:
        echo 'The makings for a sandwich are here.'

a.c:
        echo '#include <stdio.h>' > a.c
        echo 'int main() {' >> a.c
        echo '  char *txt[] = { "What? Make it yourself.", "Okay." };' >> a.c
        echo '  int n = 0;' >> a.c
        echo '  while (getchar() != EOF) n ^= 1;' >> a.c
        echo '  puts(txt[n]); return 0; }' >> a.c

me.c:
        echo '#include <stdio.h>' > me.c
        echo '#include <unistd.h>' >> me.c
        echo 'int main() {' >> me.c
        echo '  char *txt[] = { "/dev/null", "sandwich" };' >> me.c
        echo '  puts(txt[!getuid()]); return 0; }' >> me.c

sandwich:
        ./me | ./a | tee `./me`
        rm -f me a

clean:
        rm -f sandwich

(Less buggy, and hopefully a bit less boring, than my initial submission.)

\$\endgroup\$
  • \$\begingroup\$ Looks like it violates rule #2 - sudo make me sandwich. \$\endgroup\$ – ugoren Jul 30 '13 at 5:15
  • \$\begingroup\$ && test -e ./a \$\endgroup\$ – luser droog Jul 30 '13 at 6:50
  • \$\begingroup\$ Argh, you're right. This version should do a better job of observing the rules. \$\endgroup\$ – breadbox Jul 30 '13 at 8:14
  • 1
    \$\begingroup\$ you certainly have the most appropriate name, @breadbox \$\endgroup\$ – ardnew Jul 31 '13 at 13:55
9
\$\begingroup\$

A fairly compact solution that ignores guideline #2 (127 characters, including some unnecessary whitespace). I follow breadbox's practice of cleaning up after myself. Don't put any other file in the same directory as the makefile! Also, note that the makefile must be called Makefile, other names will not work.

me: ; echo echo Okay >$@
a: ; chmod u=x me
sandwich: ; ./me 2>/dev/null || echo 'What? Make it yourself'; rm -f [!M]*
.SILENT:

Usage:

$ make me a sandwich
What? Make it yourself
$ sudo make me a sandwich
Okay
$ sudo make me sandwich  
What? Make it yourself
$ sudo make a sandwich
chmod: cannot access `me': No such file or directory
make: *** [a] Error 1
$ sudo make sandwich  
What? Make it yourself

I like the way this one deals with guideline #3. Just one press of Shift makes the difference.

\$\endgroup\$
3
\$\begingroup\$

This solution doesn't adhere to guideline #2 — or does being vaguely sandwich-shaped count? I went on a kind of palindrome theme at the beginning, but I could only find so many ways to make lines symmetric without resorting to lame real code # ecod laer. I didn't take guideline #1 very seriously, aesthetics comes first.

Remember that it's a makefile; the 3 successive lines beginning with if, rm and echo begin with a tab. The other indented lines begin with spaces.

me : mr ; env | grep -q DO_US || rm -- mr || SU_OD q- perg | vne ; rm : em
                            .SILENT:##:TNELIS.
                              . = a.  .a = .
$(.:.=):;:>$@
sandwich:
    if expr>/dev/null $$(id -u); then echo 'What? Make it yourself'; fi
    rm f- a me mr 2>/dev/null
    echo Okay
mr : ; true : ; touch me : em f- mr || rm -f me : em hcuot ; : eurt ; : rm

Usage (the commands can be issued in any order):

$ sudo make me a sandwich     
Okay
$ sudo make me sandwich  
make: *** [sandwich] Error 1
$ sudo make a sandwich 
make: *** [sandwich] Error 1
$ sudo make sandwich  
make: *** [sandwich] Error 1
$ make me a sandwich
What? Make it yourself
make: *** [sandwich] Error 1

As for guideline #3, this isn't cleverly hidden, just slightly weirdly. Some hints:

There are two different tests: one search for SUDO_USER, and one test of whether id -u prints 0. Do you see why What? is not printed for root? Do you see how the lack of SUDO_USER causes Okay not to be printed?

\$\endgroup\$
2
\$\begingroup\$

Here is a first go using a different and simpler technique:

.SILENT:

reset_command:
    @rm -f command

me a sandwich: reset_command
    @echo $@ >> command
    -@make `cat command | tr "\n" "_"` 2>/dev/null

me_a_sandwich_: reset_command
    @if [ `whoami` == "root" ]; then echo "Okay"; else echo "What? Make it yourself."; fi

I'm pretty sure there is more that I can do with this - get it to respond to other input commands for instance. I'll have to work on the root switch obfuscation and the actual sandwich making.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.