Subject: bin/34662: readlink doesn't grok -f, and there's no alternative (+fix)
To: None <gnats-admin@netbsd.org, netbsd-bugs@netbsd.org>
From: None <martijnb@atlas.ipv6.stack.nl>
List: netbsd-bugs
Date: 09/29/2006 11:55:00
>Number:         34662
>Category:       bin
>Synopsis:       readlink doesn't grok -f, and there's no alternative (+fix)
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Fri Sep 29 11:55:00 +0000 2006
>Originator:     martijnb@atlas.ipv6.stack.nl
>Release:        NetBSD 4.99.3
>Organization:
	
>Environment:
	
	
System: NetBSD atlas.ipv6.stack.nl 4.99.3 NetBSD 4.99.3 (ATLAS) #4: Thu Sep 28 13:29:00 CEST 2006 martijnb@atlas.ipv6.stack.nl:/usr/obj/sys/arch/amd64/compile/ATLAS amd64
Architecture: x86_64
Machine: amd64
>Description:
	Currently, there is no userland utility to query the absolute pathname
of a given filename by means of realpath(3). Other UNIX-like operatingsystems
either offer a this by means of a realpath(1) binary (Most notably: FreeBSD,
Gentoo Linux), others (OpenBSD, all other Linux distributions)  have a '-f' 
argument to readlink(1) which does the same: Call realpath(3) on the given
argument. To my best knowledge, NetBSD offers neither.
>How-To-Repeat:
	observation. There are programs out there relying on the availability
of this functionality.
>Fix:
	There's three possible approaches:

1)	Do what FreeBSD does: leave stat/readlink as they are, and write a
	small wrapper program to call realpath(3).
2)	Do what OpenBSD/Linux does: Make readlink a seperate program (It is
	currently a symlink to stat) and handle the '-f' case accordingly.  
3)	Keep the current situation (readlink being a symlink to stat), add
	yet another format option to stat and update the readlink stub to 
	accept a '-f' argument.

I considered #3 to be the best option, and introduced a "%y" format option.

Index: stat.1
===================================================================
RCS file: /cvsroot/src/usr.bin/stat/stat.1,v
retrieving revision 1.18
diff -u -r1.18 stat.1
--- stat.1	26 Jun 2005 10:16:46 -0000	1.18
+++ stat.1	29 Sep 2006 11:30:32 -0000
@@ -54,7 +54,7 @@
 .Op Fl t Ar timefmt
 .Op Ar
 .Nm readlink
-.Op Fl n
+.Op Fl nf
 .Op Ar
 .Sh DESCRIPTION
 The
@@ -71,9 +71,19 @@
 When invoked as
 .Nm readlink ,
 only the target of the symbolic link is printed.
-If the given argument is not a symbolic link,
+If the given argument is not a symbolic link and the
+.Fl f
+option is not specified,
 .Nm readlink
-will print nothing and exit with an error.
+will print nothing and exit with an error. If the
+.Fl f
+option is specified, the output is canonicalized by following every symlink
+in every component of the given path recursively. 
+.Nm readlink
+will resolve both absolute and relative paths, and return the absolute pathname
+corresponding to
+.Ar file .
+In this case, the argument does not need to be a symbolic link.
 .Pp
 The information displayed is obtained by calling
 .Xr lstat 2
@@ -379,7 +389,7 @@
 Inode generation number.
 .El
 .Pp
-The following four field specifiers are not drawn directly from the
+The following five field specifiers are not drawn directly from the
 data in struct stat, but are:
 .Bl -tag -width Ds
 .It Cm N
@@ -390,6 +400,8 @@
 or in a more descriptive form if the sub field specifier
 .Cm H
 is given.
+.It Cm y
+The absolute pathname corresponding to the file.
 .It Cm Y
 The target of a symbolic link.
 .It Cm Z
Index: stat.c
===================================================================
RCS file: /cvsroot/src/usr.bin/stat/stat.c,v
retrieving revision 1.23
diff -u -r1.23 stat.c
--- stat.c	23 Jun 2005 03:13:24 -0000	1.23
+++ stat.c	29 Sep 2006 11:30:32 -0000
@@ -176,6 +176,7 @@
 #define SHOW_st_flags	'f'
 #define SHOW_st_gen	'v'
 #define SHOW_symlink	'Y'
+#define SHOW_realpath   'y'
 #define SHOW_filetype	'T'
 #define SHOW_filename	'N'
 #define SHOW_sizerdev	'Z'
@@ -219,8 +220,8 @@
 
 	if (strcmp(getprogname(), "readlink") == 0) {
 		am_readlink = 1;
-		options = "n";
-		synopsis = "[-n] [file ...]";
+		options = "nf";
+		synopsis = "[-nf] [file ...]";
 		statfmt = "%Y";
 		fmtchar = 'f';
 		quiet = 1;
@@ -244,6 +245,10 @@
 			quiet = 1;
 			break;
 		case 'f':
+			if(am_readlink) {
+				statfmt="%y";
+				break;
+			}
 			statfmt = optarg;
 			/* FALLTHROUGH */
 		case 'l':
@@ -511,6 +516,7 @@
 			fmtcase(what, SHOW_st_flags);
 			fmtcase(what, SHOW_st_gen);
 			fmtcase(what, SHOW_symlink);
+			fmtcase(what, SHOW_realpath);
 			fmtcase(what, SHOW_filetype);
 			fmtcase(what, SHOW_filename);
 			fmtcase(what, SHOW_sizerdev);
@@ -784,6 +790,21 @@
 			ofmt = FMTF_UNSIGNED;
 		break;
 #endif /* HAVE_STRUCT_STAT_ST_GEN */
+	case SHOW_realpath:
+		small = 0;
+		data = 0;
+		snprintf(path, sizeof(path), " -> ");
+		if (realpath(file, path + 4) == NULL ) {
+			linkfail = 1;
+			l = 0;
+			path[0] = '\0';
+		}
+		sdata = path + (ofmt == FMTF_STRING ? 0: 4);
+		
+		formats = FMTF_STRING;
+		if (ofmt == 0)
+			ofmt = FMTF_STRING;
+		break;
 	case SHOW_symlink:
 		small = 0;
 		data = 0;

>Unformatted: