+Support '/dev/ataraid/d0p1' device naming.
[captive.git] / src / libcaptive / storage / relastblock.c
1 /* $Id$
2  * Workaround Linux kernel bug wrt accessing last device block for libcaptive
3  * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include "iounixchannel.h"      /* self */
23 #include <glib/gmessages.h>
24 #include <glib/giochannel.h>
25 #include <glib/gtypes.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include "size.h"
29 #include <linux/hdreg.h>
30 #include <sys/ioctl.h>
31 #include "captive/macros.h"
32 #include <ctype.h>
33 #include "../client/lib.h"      /* for captive_giochannel_setup(); FIXME: pathname */
34 #include "../client/giochannel-subrange.h"
35 #include "captive/storage.h"    /* for captive_giochannel_size() */
36
37
38 /* Config */
39 #define ENSURE_BLOCK_SIZE 0x200 /* size of the NTFS 'superblock backup' */
40
41
42
43 gboolean start_bytes_ioctl_detect(int fd,guint64 *start_bytes_ioctlp)
44 {
45 struct hd_geometry hd_geometry;
46 #ifdef HDIO_GETGEO_BIG
47 struct hd_big_geometry hd_big_geometry;
48 #endif
49 unsigned long start_sectors_ioctl;
50
51         /* 'start' field is always 'unsigned long'.
52          * 'HDIO_GETGEO_BIG' is supported if 'HDIO_GETGEO' gets deprecated.
53          */
54         if (0)
55                 /* nop */;
56 #ifdef HDIO_GETGEO_BIG
57         else if (!ioctl(fd,HDIO_GETGEO_BIG,&hd_big_geometry))
58                 start_sectors_ioctl=hd_big_geometry.start;
59 #endif
60         else if (!ioctl(fd,HDIO_GETGEO,&hd_geometry))
61                 start_sectors_ioctl=hd_geometry.start;
62         else
63                 return FALSE;
64
65         *start_bytes_ioctlp=((guint64)start_sectors_ioctl)*0x200;
66         return TRUE;
67 }
68
69 static gboolean check_last_block(GIOChannel *iochannel,guint64 iochannel_size)
70 {
71 GIOStatus erriostatus;
72 gchar buf[ENSURE_BLOCK_SIZE];
73 gsize bufgot;
74
75         g_return_val_if_fail(iochannel!=NULL,FALSE);
76         g_return_val_if_fail(iochannel_size>0,FALSE);
77
78         erriostatus=g_io_channel_seek_position(iochannel,iochannel_size-ENSURE_BLOCK_SIZE,G_SEEK_SET,
79                         NULL);  /* error */
80         g_assert(erriostatus==G_IO_STATUS_NORMAL);
81
82         erriostatus=g_io_channel_read_chars(iochannel,
83                         buf, /* buf */
84                         ENSURE_BLOCK_SIZE,      /* count */
85                         &bufgot,        /* bytes_read */
86                         NULL);  /* error */
87         /* During read on the end boundary of Linux kernel block device we will
88          * get GNOME_VFS_ERROR_IO at least from linux-kernel-2.4.19-ac4
89          * which will get mapped to G_IO_STATUS_ERROR by captive_gnomevfs_giognomevfs_io_read().
90          */
91         g_assert(0
92                         || (bufgot==ENSURE_BLOCK_SIZE && erriostatus==G_IO_STATUS_NORMAL)
93                         || (bufgot==0                 && erriostatus==G_IO_STATUS_EOF)
94                         || (bufgot==0                 && erriostatus==G_IO_STATUS_ERROR));
95
96         return (erriostatus==G_IO_STATUS_NORMAL);
97 }
98
99 /* No new reference is created; unref the result the same way as the input. */
100 GIOChannel *captive_storage_relastblock(GIOChannel *iochannel)
101 {
102 int fd;
103 guint64 size_bytes_ioctl;
104 guint64 start_bytes_ioctl;
105 char linkbuf[0x1000],*linknum;
106 int linkgot;
107 const gchar *iochannel_unix_new_mode;
108 const gchar *linkread_pathname;
109 const gchar *slashpart_prefix;
110 gboolean errbool;
111 GIOFlags iochannel_flags;
112 GIOChannel *iochannel_unix_new;
113 int iochannel_unix_new_fd;
114 guint64 unix_new_start_bytes_ioctl,unix_new_size_bytes_ioctl;
115 GIOChannel *iochannel_subrange_new;
116
117         g_return_val_if_fail(iochannel!=NULL,NULL);
118
119         if (-1==(fd=captive_iounixchannel_get_fd(iochannel)))
120                 return iochannel;
121
122         if (ENSURE_BLOCK_SIZE>(size_bytes_ioctl=captive_giochannel_size_ioctl(iochannel)))
123                 return iochannel;
124
125         g_return_val_if_fail(g_io_channel_get_encoding(iochannel)==NULL,0);
126
127         if (!start_bytes_ioctl_detect(fd,&start_bytes_ioctl))
128                 return iochannel;
129
130         if (check_last_block(iochannel,size_bytes_ioctl))
131                 return iochannel;
132
133         linkread_pathname=captive_printf_alloca("/proc/self/fd/%d",fd);
134         linkgot=readlink(
135                         linkread_pathname,      /* path */
136                         linkbuf,        /* buf */
137                         sizeof(linkbuf));       /* bufsiz */
138         if (!(linkgot>=1 && linkgot<=(int)sizeof(linkbuf)-1))
139                 g_error(_("Unable to read: %s"),linkread_pathname);
140         linkbuf[linkgot]=0;     /* readlink(2) does not '\0'-terminate it */
141
142         for (linknum=linkbuf+linkgot;linknum>linkbuf && isdigit(linknum[-1]);linknum--);
143         if (linknum>=linkbuf+linkgot)
144                 g_error(_("Last block not readable although the link read has no trailing number: %s"),linkbuf);
145         *linknum='\0';
146
147         /* /dev/ide/host0/bus0/target0/lun0/part1 -> /dev/ide/host0/bus0/target0/lun0/disc */
148         slashpart_prefix="/part";
149         if (linknum>linkbuf+strlen(slashpart_prefix) && !strcmp(linknum-strlen(slashpart_prefix),slashpart_prefix))
150                 strcpy(linknum-strlen(slashpart_prefix),"/disc");
151
152         /* /dev/ataraid/d0p1 -> /dev/ataraid/d0 */
153         if (linknum>=linkbuf+2 && linknum[-1]=='p' && isdigit(linknum[-2]))
154                 *--linknum='\0';
155
156         iochannel_flags=g_io_channel_get_flags(iochannel);
157         switch (iochannel_flags & (G_IO_FLAG_IS_READABLE|G_IO_FLAG_IS_WRITEABLE)) {
158                 case G_IO_FLAG_IS_READABLE:
159                         iochannel_unix_new_mode="r";
160                         break;
161                 case G_IO_FLAG_IS_READABLE|G_IO_FLAG_IS_WRITEABLE:
162                         iochannel_unix_new_mode="r+";
163                         break;
164                 default: g_assert_not_reached();
165                 }
166
167         if (!(iochannel_unix_new=g_io_channel_new_file(
168                         linkbuf,        /* filename */
169                         iochannel_unix_new_mode,        /* mode */
170                         NULL))) /* error */
171                 g_error(_("Parent partition \"%s\" not readable by mode \"%s\""),linkbuf,iochannel_unix_new_mode);
172
173         /* 'iochannel_unix_new' sanity checks: */
174         iochannel_unix_new_fd=captive_iounixchannel_get_fd(iochannel_unix_new);
175         g_assert(iochannel_unix_new_fd>=0);
176         errbool=start_bytes_ioctl_detect(iochannel_unix_new_fd,&unix_new_start_bytes_ioctl);
177         g_assert(errbool==TRUE);
178         g_assert(unix_new_start_bytes_ioctl==0);
179         unix_new_size_bytes_ioctl=captive_giochannel_size_ioctl(iochannel_unix_new);
180         g_assert(unix_new_size_bytes_ioctl>0);
181         if (!(unix_new_size_bytes_ioctl>=start_bytes_ioctl+size_bytes_ioctl))
182                 g_error(_("Partition last block inaccessible and partition table incorrect: disc size %llu < part start %llu + part size %llu"),
183                 (unsigned long long)unix_new_size_bytes_ioctl,
184                 (unsigned long long)start_bytes_ioctl,
185                 (unsigned long long)size_bytes_ioctl);
186
187         iochannel_subrange_new=(GIOChannel *)captive_giochannel_subrange_new(iochannel_unix_new,
188                         start_bytes_ioctl,      /* start */
189                         start_bytes_ioctl+size_bytes_ioctl);    /* end */
190         g_assert(iochannel_subrange_new!=NULL);
191
192         g_io_channel_unref(iochannel_unix_new); /* now reffed by 'iochannel_subrange_new' */
193
194         captive_giochannel_setup(iochannel_subrange_new);
195         g_io_channel_unref(iochannel);
196
197         if (!check_last_block(iochannel_subrange_new,size_bytes_ioctl))
198                 g_error(_("Last block still not readable for the subrange of: %s"),linkbuf);
199
200         if (size_bytes_ioctl!=captive_giochannel_size(iochannel_subrange_new))
201                 g_error(_("Invalid size of the subranged GIOChannel of: %s"),linkbuf);
202
203         return iochannel_subrange_new;
204 }