Use older 2TB-limited 'BLKGETSIZE' if 'BLKGETSIZE64' is not available.
[captive.git] / src / libcaptive / storage / size.c
1 /* $Id$
2  * Detect media size of given GIOChannel for libcaptive
3  * Copyright (C) 2002 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 "captive/storage.h"    /* self */
23 #include "../client/giochannel-blind.h" /* for captive_giochannel_blind_get_size() */
24 #include "../sandbox/client-CaptiveIOChannel.h" /* for captive_io_channel_get_size() */
25 #include <glib/gmessages.h>
26 #include <glib/gtypes.h>
27 #include <fcntl.h>
28 #include <sys/ioctl.h>
29 #include <unistd.h>
30 #include <linux/types.h>        /* for __u64 for u64 for BLKGETSIZE64 */
31 #define u64 __u64
32 #include <linux/fs.h>   /* for BLKGETSIZE64 */
33
34
35 static guint64 size_blind(GIOChannel *iochannel)
36 {
37 guint64 r;
38
39         g_return_val_if_fail(iochannel!=NULL,0);
40
41         if (!captive_giochannel_blind_get_size(iochannel,&r))
42                 return 0;
43
44         return r;
45 }
46
47
48 static guint64 size_sandbox(GIOChannel *iochannel)
49 {
50 guint64 r;
51
52         g_return_val_if_fail(iochannel!=NULL,0);
53
54         if (!captive_io_channel_get_size(iochannel,&r))
55                 return 0;
56
57         return r;
58 }
59
60
61 static GIOChannel *iochannel_null;
62
63 static int iounixchannel_get_fd(GIOChannel *iochannel)
64 {
65 int r;
66
67         g_return_val_if_fail(iochannel!=NULL,-1);
68
69         if (!iochannel_null) {
70 int fd;
71
72                 fd=open("/dev/null",O_RDONLY);
73                 g_return_val_if_fail(fd!=-1,-1);
74                 iochannel_null=g_io_channel_unix_new(fd);
75                 g_return_val_if_fail(iochannel_null!=NULL,-1);
76                 }
77
78         if (iochannel->funcs!=iochannel_null->funcs) {
79                 /* Not a UNIX file descriptor */
80                 return -1;
81                 }
82
83         /* It is forbidden to callg_io_channel_unix_get_fd()
84          * if you are not sure it is a 'GIOUnixChannel'.
85          */
86         r=g_io_channel_unix_get_fd(iochannel);
87         g_return_val_if_fail(r!=-1,-1);
88
89         return r;
90 }
91
92
93 static guint64 size_ioctl(GIOChannel *iochannel)
94 {
95 int fd;
96 guint64 r;
97 #ifndef BLKGETSIZE64
98 long r_long;
99 #endif
100
101         g_return_val_if_fail(iochannel!=NULL,0);
102
103         if (-1==(fd=iounixchannel_get_fd(iochannel)))
104                 return 0;
105
106 #ifdef BLKGETSIZE64
107         if (ioctl(fd,BLKGETSIZE64,&r))
108                 return 0;
109 #else
110         if (ioctl(fd,BLKGETSIZE,&r_long))
111                 return 0;
112         if (r_long<0)
113                 return 0;
114         r=((guint64)512)*r_long;
115 #endif
116
117         return r;
118 }
119
120
121 static guint64 size_seek(GIOChannel *iochannel)
122 {
123 int fd;
124 off_t offset_orig,offset;
125
126         g_return_val_if_fail(iochannel!=NULL,0);
127
128         /* We may need '_FILE_OFFSET_BITS=64'.
129          * Setting '__USE_FILE_OFFSET64' did not help.
130          * Done by 'AC_SYS_LARGEFILE' of configure.in.
131          */
132         g_return_val_if_fail(sizeof(offset)==sizeof(guint64),0);
133
134         if (-1==(fd=iounixchannel_get_fd(iochannel)))
135                 return 0;
136
137         if (-1==(offset_orig=lseek(fd,0,SEEK_CUR)))
138                 return 0;
139         g_return_val_if_fail(offset_orig>=0,0);
140         offset=lseek(fd,0,SEEK_END);
141         if (offset_orig!=lseek(fd,offset_orig,SEEK_SET))
142                 g_assert_not_reached();
143
144         if (-1==offset)
145                 return 0;
146         g_return_val_if_fail(offset>=0,0);
147
148         return offset;
149 }
150
151
152
153 static guint64 size_read(GIOChannel *iochannel)
154 {
155 gint64 low,high,mid;
156 GIOStatus erriostatus;
157 gchar bufchar;
158 gsize bufchargot;
159
160         g_return_val_if_fail(iochannel!=NULL,0);
161         /* Default "UTF-8" encoding is not much usable for us */
162         g_return_val_if_fail(g_io_channel_get_encoding(iochannel)==NULL,0);
163
164         /* low  ==high: low (high)
165          * low+1==high: mid==low; NORMAL: no change: high
166          * low+1==high: mid==low; EOF   : high=mid => 'low==high' case
167          */
168         for (low=0,
169                         high=
170                                         G_MAXINT64;
171                         low+1<high;) {
172                 mid=low+(high-low)/2;   /* beware of 'gint64' overflow! */
173
174                 erriostatus=g_io_channel_seek_position(iochannel,mid,G_SEEK_SET,
175                                 NULL);  /* error */
176                 /* During seek in block device such as on URL file:///dev/hda1#captive-fastfat.sys-ro:/
177                  * we will do llseek(2) on "/dev/hda1" device from captive_giochannel_size().
178                  * Although we are allowed to seek behind EOF on regular files
179                  * at least linux-kernel-2.4.19-ac4/fs/block_dev.c/block_llseek() will give
180                  * EINVAL on seek behind EOF therefore it must be accepted without complaints by us.
181                  */
182                 if (erriostatus!=G_IO_STATUS_NORMAL) {
183                         erriostatus=G_IO_STATUS_EOF;
184                         bufchargot=0;
185                         }
186                 else {
187                         g_assert(sizeof(bufchar)==1);
188                         erriostatus=g_io_channel_read_chars(iochannel,
189                                         &bufchar, /* buf */
190                                         sizeof(bufchar),        /* count */
191                                         &bufchargot,    /* bytes_read */
192                                         NULL);  /* error */
193                         /* During read on the end boundary of Linux kernel block device we will
194                          * get GNOME_VFS_ERROR_IO at least from linux-kernel-2.4.19-ac4
195                          * which will get mapped to G_IO_STATUS_ERROR by captive_gnomevfs_giognomevfs_io_read().
196                          */
197                         g_assert(0
198                                         || erriostatus==G_IO_STATUS_NORMAL
199                                         || erriostatus==G_IO_STATUS_EOF
200                                         || erriostatus==G_IO_STATUS_ERROR);
201                         g_assert(0
202                                         || (erriostatus==G_IO_STATUS_NORMAL && bufchargot==1)
203                                         || (erriostatus==G_IO_STATUS_EOF    && bufchargot==0)
204                                         || (erriostatus==G_IO_STATUS_ERROR  && bufchargot==0));
205                         }
206
207                 if (erriostatus==G_IO_STATUS_NORMAL)
208                         low=mid;
209                 else
210                         high=mid;
211                 }
212         
213         g_assert(high>=0);
214         return high;
215 }
216
217
218 guint64 captive_giochannel_size(GIOChannel *iochannel)
219 {
220 guint64 r;
221
222         if ((r=size_blind(iochannel)))
223                 return r;
224         if ((r=size_sandbox(iochannel)))
225                 return r;
226         if ((r=size_ioctl(iochannel)))
227                 return r;
228         if ((r=size_seek(iochannel)))
229                 return r;
230         if ((r=size_read(iochannel)))
231                 return r;
232
233         return r;
234 }